From f9aea72d159e58cee9dafe76ac65d081b1ad8fa2 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 14 Apr 2026 10:52:01 +0200 Subject: [PATCH 01/12] Split fuzz runners by hash mode Move shared fuzz logic into the root fuzz crate and generate fake-hashes and real-hashes runner crates. Keep `chanmon_consistency_target` on the real-hashes side, remove the fuzz-local Cargo config, and update scripts, CI, coverage, and docs to use explicit flags for each runner. Generate the hash-mode compile checks in the wrapper bins without a synthetic Cargo feature, while keeping the wrapper template close to its original shape. AI tools were used in preparing this commit. --- .github/workflows/build.yml | 6 +- ci/check-compiles.sh | 6 +- contrib/generate_fuzz_coverage.sh | 46 +++-- fuzz/.cargo/config.toml | 2 - fuzz/Cargo.toml | 21 +-- fuzz/README.md | 47 +++-- fuzz/ci-fuzz.sh | 101 ++++++----- fuzz/fuzz-fake-hashes/Cargo.toml | 31 ++++ .../src/bin/base32_target.rs | 6 +- .../src/bin/bech32_parse_target.rs | 6 +- .../src/bin/bolt11_deser_target.rs | 6 +- .../src/bin/chanmon_deser_target.rs | 6 +- .../src/bin/feature_flags_target.rs | 6 +- .../src/bin/fromstr_to_netaddress_target.rs | 6 +- .../src/bin/fs_store_target.rs | 6 +- .../src/bin/full_stack_target.rs | 6 +- .../src/bin/gossip_discovery_target.rs | 6 +- .../src/bin/indexedmap_target.rs | 6 +- .../src/bin/invoice_deser_target.rs | 6 +- .../src/bin/invoice_request_deser_target.rs | 6 +- .../src/bin/lsps_message_target.rs | 6 +- .../src/bin/msg_accept_channel_target.rs | 6 +- .../src/bin/msg_accept_channel_v2_target.rs | 6 +- .../bin/msg_announcement_signatures_target.rs | 6 +- .../bin/msg_blinded_message_path_target.rs | 6 +- .../bin/msg_channel_announcement_target.rs | 6 +- .../src/bin/msg_channel_details_target.rs | 6 +- .../src/bin/msg_channel_ready_target.rs | 6 +- .../src/bin/msg_channel_reestablish_target.rs | 6 +- .../src/bin/msg_channel_update_target.rs | 6 +- .../src/bin/msg_closing_complete_target.rs | 6 +- .../src/bin/msg_closing_sig_target.rs | 6 +- .../src/bin/msg_closing_signed_target.rs | 6 +- .../src/bin/msg_commitment_signed_target.rs | 6 +- .../msg_decoded_onion_error_packet_target.rs | 6 +- .../src/bin/msg_error_message_target.rs | 6 +- .../src/bin/msg_funding_created_target.rs | 6 +- .../src/bin/msg_funding_signed_target.rs | 6 +- .../bin/msg_gossip_timestamp_filter_target.rs | 6 +- .../src/bin/msg_init_target.rs | 6 +- .../src/bin/msg_node_announcement_target.rs | 6 +- .../src/bin/msg_open_channel_target.rs | 6 +- .../src/bin/msg_open_channel_v2_target.rs | 6 +- .../src/bin/msg_ping_target.rs | 6 +- .../src/bin/msg_pong_target.rs | 6 +- .../src/bin/msg_query_channel_range_target.rs | 6 +- .../bin/msg_query_short_channel_ids_target.rs | 6 +- .../src/bin/msg_reply_channel_range_target.rs | 6 +- .../msg_reply_short_channel_ids_end_target.rs | 6 +- .../src/bin/msg_revoke_and_ack_target.rs | 6 +- .../src/bin/msg_shutdown_target.rs | 6 +- .../src/bin/msg_splice_ack_target.rs | 6 +- .../src/bin/msg_splice_init_target.rs | 6 +- .../src/bin/msg_splice_locked_target.rs | 6 +- .../src/bin/msg_stfu_target.rs | 6 +- .../src/bin/msg_tx_abort_target.rs | 6 +- .../src/bin/msg_tx_ack_rbf_target.rs | 6 +- .../src/bin/msg_tx_add_input_target.rs | 6 +- .../src/bin/msg_tx_add_output_target.rs | 6 +- .../src/bin/msg_tx_complete_target.rs | 6 +- .../src/bin/msg_tx_init_rbf_target.rs | 6 +- .../src/bin/msg_tx_remove_input_target.rs | 6 +- .../src/bin/msg_tx_remove_output_target.rs | 6 +- .../src/bin/msg_tx_signatures_target.rs | 6 +- .../src/bin/msg_update_add_htlc_target.rs | 6 +- .../src/bin/msg_update_fail_htlc_target.rs | 6 +- .../msg_update_fail_malformed_htlc_target.rs | 6 +- .../src/bin/msg_update_fee_target.rs | 6 +- .../src/bin/msg_update_fulfill_htlc_target.rs | 6 +- .../src/bin/offer_deser_target.rs | 6 +- .../src/bin/onion_hop_data_target.rs | 6 +- .../src/bin/onion_message_target.rs | 6 +- .../src/bin/peer_crypt_target.rs | 6 +- .../src/bin/process_network_graph_target.rs | 6 +- .../src/bin/process_onion_failure_target.rs | 6 +- .../src/bin/refund_deser_target.rs | 6 +- .../src/bin/router_target.rs | 6 +- .../src/bin/static_invoice_deser_target.rs | 6 +- .../src/bin/zbase32_target.rs | 6 +- fuzz/fuzz-real-hashes/Cargo.toml | 31 ++++ .../src/bin/chanmon_consistency_target.rs | 8 +- fuzz/src/bin/gen_target.sh | 163 ++++++++++-------- fuzz/src/bin/target_template.txt | 8 +- fuzz/src/lib.rs | 3 - fuzz/test_cases/base32/smoke | 1 + fuzz/test_cases/bech32_parse/smoke | 1 + fuzz/test_cases/chanmon_consistency/smoke | 1 + fuzz/write-seeds/Cargo.toml | 4 - 88 files changed, 513 insertions(+), 393 deletions(-) delete mode 100644 fuzz/.cargo/config.toml create mode 100644 fuzz/fuzz-fake-hashes/Cargo.toml rename fuzz/{ => fuzz-fake-hashes}/src/bin/base32_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/bech32_parse_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/bolt11_deser_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/chanmon_deser_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/feature_flags_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/fromstr_to_netaddress_target.rs (94%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/fs_store_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/full_stack_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/gossip_discovery_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/indexedmap_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/invoice_deser_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/invoice_request_deser_target.rs (94%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/lsps_message_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_accept_channel_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_accept_channel_v2_target.rs (94%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_announcement_signatures_target.rs (94%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_blinded_message_path_target.rs (94%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_channel_announcement_target.rs (94%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_channel_details_target.rs (94%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_channel_ready_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_channel_reestablish_target.rs (94%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_channel_update_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_closing_complete_target.rs (94%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_closing_sig_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_closing_signed_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_commitment_signed_target.rs (94%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_decoded_onion_error_packet_target.rs (94%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_error_message_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_funding_created_target.rs (94%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_funding_signed_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_gossip_timestamp_filter_target.rs (94%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_init_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_node_announcement_target.rs (94%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_open_channel_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_open_channel_v2_target.rs (94%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_ping_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_pong_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_query_channel_range_target.rs (94%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_query_short_channel_ids_target.rs (94%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_reply_channel_range_target.rs (94%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_reply_short_channel_ids_end_target.rs (94%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_revoke_and_ack_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_shutdown_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_splice_ack_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_splice_init_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_splice_locked_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_stfu_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_tx_abort_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_tx_ack_rbf_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_tx_add_input_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_tx_add_output_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_tx_complete_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_tx_init_rbf_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_tx_remove_input_target.rs (94%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_tx_remove_output_target.rs (94%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_tx_signatures_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_update_add_htlc_target.rs (94%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_update_fail_htlc_target.rs (94%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_update_fail_malformed_htlc_target.rs (94%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_update_fee_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/msg_update_fulfill_htlc_target.rs (94%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/offer_deser_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/onion_hop_data_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/onion_message_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/peer_crypt_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/process_network_graph_target.rs (94%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/process_onion_failure_target.rs (94%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/refund_deser_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/router_target.rs (95%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/static_invoice_deser_target.rs (94%) rename fuzz/{ => fuzz-fake-hashes}/src/bin/zbase32_target.rs (95%) create mode 100644 fuzz/fuzz-real-hashes/Cargo.toml rename fuzz/{ => fuzz-real-hashes}/src/bin/chanmon_consistency_target.rs (94%) create mode 100644 fuzz/test_cases/base32/smoke create mode 100644 fuzz/test_cases/bech32_parse/smoke create mode 100644 fuzz/test_cases/chanmon_consistency/smoke diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b68d545ac3e..61df2601d92 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -93,7 +93,8 @@ jobs: # Could you use this to fake the coverage report for your PR? Sure. # Will anyone be impressed by your amazing coverage? No # Maybe if codecov wasn't broken we wouldn't need to do this... - ./codecov --verbose upload-process --disable-search --fail-on-error -f fuzz-codecov.json -t "f421b687-4dc2-4387-ac3d-dc3b2528af57" -F 'fuzzing' + ./codecov --verbose upload-process --disable-search --fail-on-error -f fuzz-fake-hashes-codecov.json -t "f421b687-4dc2-4387-ac3d-dc3b2528af57" -F 'fuzzing-fake-hashes' + ./codecov --verbose upload-process --disable-search --fail-on-error -f fuzz-real-hashes-codecov.json -t "f421b687-4dc2-4387-ac3d-dc3b2528af57" -F 'fuzzing-real-hashes' benchmark: runs-on: ubuntu-latest @@ -218,7 +219,8 @@ jobs: - name: Sanity check fuzz targets on Rust ${{ env.TOOLCHAIN }} run: | cd fuzz - cargo test --quiet --color always --lib --bins -j8 + RUSTFLAGS="--cfg=fuzzing --cfg=secp256k1_fuzz --cfg=hashes_fuzz" cargo test --manifest-path fuzz-fake-hashes/Cargo.toml --quiet --color always --bins -j8 + RUSTFLAGS="--cfg=fuzzing --cfg=secp256k1_fuzz" cargo test --manifest-path fuzz-real-hashes/Cargo.toml --quiet --color always --bins -j8 fuzz: runs-on: self-hosted diff --git a/ci/check-compiles.sh b/ci/check-compiles.sh index a067861fb56..cd1e0759c63 100755 --- a/ci/check-compiles.sh +++ b/ci/check-compiles.sh @@ -5,6 +5,10 @@ echo "Testing $(git log -1 --oneline)" cargo check cargo doc cargo doc --document-private-items -cd fuzz && RUSTFLAGS="--cfg=fuzzing --cfg=secp256k1_fuzz --cfg=hashes_fuzz" cargo check --features=stdin_fuzz +cd fuzz +RUSTFLAGS="--cfg=fuzzing --cfg=secp256k1_fuzz --cfg=hashes_fuzz" \ + cargo check --manifest-path fuzz-fake-hashes/Cargo.toml --features=stdin_fuzz +RUSTFLAGS="--cfg=fuzzing --cfg=secp256k1_fuzz" \ + cargo check --manifest-path fuzz-real-hashes/Cargo.toml --features=stdin_fuzz cd ../lightning && cargo check --no-default-features cd .. && RUSTC_BOOTSTRAP=1 RUSTFLAGS="--cfg=c_bindings" cargo check -Z avoid-dev-deps diff --git a/contrib/generate_fuzz_coverage.sh b/contrib/generate_fuzz_coverage.sh index 6be9956bbca..790fdb25f01 100755 --- a/contrib/generate_fuzz_coverage.sh +++ b/contrib/generate_fuzz_coverage.sh @@ -55,18 +55,37 @@ fi # Create output directory if it doesn't exist mkdir -p "$OUTPUT_DIR" +generate_coverage_report() { + local manifest_path="$1" + local output_path="$2" + local rustflags="$3" + + cargo llvm-cov clean --workspace + RUSTFLAGS="$rustflags" cargo llvm-cov --manifest-path "$manifest_path" --codecov \ + --dep-coverage lightning,lightning-invoice,lightning-liquidity,lightning-rapid-gossip-sync,lightning-persister \ + --no-default-ignore-filename-regex \ + --ignore-filename-regex "(\.cargo/registry|\.rustup/toolchains|/fuzz/)" \ + --output-path "$output_path" --tests +} + # dont run this command when running in CI if [ "$OUTPUT_CODECOV_JSON" = "0" ]; then - cargo llvm-cov --html \ + cargo llvm-cov clean --workspace + RUSTFLAGS="--cfg=fuzzing --cfg=secp256k1_fuzz --cfg=hashes_fuzz" \ + cargo llvm-cov --manifest-path fuzz-fake-hashes/Cargo.toml --html \ --dep-coverage lightning,lightning-invoice,lightning-liquidity,lightning-rapid-gossip-sync,lightning-persister \ --no-default-ignore-filename-regex \ --ignore-filename-regex "(\.cargo/registry|\.rustup/toolchains|/fuzz/)" \ - --output-dir "$OUTPUT_DIR" - echo "Coverage report generated in $OUTPUT_DIR/html/index.html" -else - # Clean previous coverage artifacts to ensure a fresh run. + --output-dir "$OUTPUT_DIR/fake-hashes" --tests cargo llvm-cov clean --workspace - + RUSTFLAGS="--cfg=fuzzing --cfg=secp256k1_fuzz" \ + cargo llvm-cov --manifest-path fuzz-real-hashes/Cargo.toml --html \ + --dep-coverage lightning,lightning-invoice,lightning-liquidity,lightning-rapid-gossip-sync,lightning-persister \ + --no-default-ignore-filename-regex \ + --ignore-filename-regex "(\.cargo/registry|\.rustup/toolchains|/fuzz/)" \ + --output-dir "$OUTPUT_DIR/real-hashes" --tests + echo "Coverage reports generated in $OUTPUT_DIR/fake-hashes and $OUTPUT_DIR/real-hashes" +else # Import honggfuzz corpus if the artifact was downloaded. if [ -d "hfuzz_workspace" ]; then echo "Importing corpus from hfuzz_workspace..." @@ -82,11 +101,14 @@ else fi echo "Replaying imported corpus (if found) via tests to generate coverage..." - cargo llvm-cov -j8 --codecov \ - --dep-coverage lightning,lightning-invoice,lightning-liquidity,lightning-rapid-gossip-sync,lightning-persister \ - --no-default-ignore-filename-regex \ - --ignore-filename-regex "(\.cargo/registry|\.rustup/toolchains|/fuzz/)" \ - --output-path "$OUTPUT_DIR/fuzz-codecov.json" --tests + generate_coverage_report \ + "fuzz-fake-hashes/Cargo.toml" \ + "$OUTPUT_DIR/fuzz-fake-hashes-codecov.json" \ + "--cfg=fuzzing --cfg=secp256k1_fuzz --cfg=hashes_fuzz" + generate_coverage_report \ + "fuzz-real-hashes/Cargo.toml" \ + "$OUTPUT_DIR/fuzz-real-hashes-codecov.json" \ + "--cfg=fuzzing --cfg=secp256k1_fuzz" - echo "Fuzz codecov report available at $OUTPUT_DIR/fuzz-codecov.json" + echo "Fuzz codecov reports available at $OUTPUT_DIR/fuzz-fake-hashes-codecov.json and $OUTPUT_DIR/fuzz-real-hashes-codecov.json" fi diff --git a/fuzz/.cargo/config.toml b/fuzz/.cargo/config.toml deleted file mode 100644 index 86513788566..00000000000 --- a/fuzz/.cargo/config.toml +++ /dev/null @@ -1,2 +0,0 @@ -[build] -rustflags = ["--cfg=fuzzing", "--cfg=secp256k1_fuzz", "--cfg=hashes_fuzz"] diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 252946be458..8cafdd1f2fb 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -4,18 +4,6 @@ version = "0.0.1" authors = ["Automatically generated"] publish = false edition = "2021" -# Because the function is unused it gets dropped before we link lightning, so -# we have to duplicate build.rs here. Note that this is only required for -# fuzzing mode. - -[package.metadata] -cargo-fuzz = true - -[features] -afl_fuzz = ["afl"] -honggfuzz_fuzz = ["honggfuzz"] -libfuzzer_fuzz = ["libfuzzer-sys"] -stdin_fuzz = [] [dependencies] lightning = { path = "../lightning", features = ["regex", "_test_utils"] } @@ -27,16 +15,9 @@ bech32 = "0.11.0" bitcoin = { version = "0.32.4", features = ["secp-lowmemory"] } tokio = { version = "~1.35", default-features = false, features = ["rt-multi-thread"] } -afl = { version = "0.12", optional = true } -honggfuzz = { version = "0.5", optional = true, default-features = false } -libfuzzer-sys = { version = "0.4", optional = true } - -[build-dependencies] -cc = "1.0" - # Prevent this from interfering with workspaces [workspace] -members = ["."] +members = [".", "fuzz-fake-hashes", "fuzz-real-hashes", "write-seeds"] [profile.release] panic = "abort" diff --git a/fuzz/README.md b/fuzz/README.md index 4af70390d7d..608b7661304 100644 --- a/fuzz/README.md +++ b/fuzz/README.md @@ -10,6 +10,11 @@ configured for. Fuzzing is further only effective with a lot of CPU time, indica scenarios are discovered on CI with its low runtime constraints, the crash is caused relatively easily. +The `fuzz/` directory now contains three crates: +- `fuzz/`, the shared fuzz target logic and corpus directories +- `fuzz/fuzz-fake-hashes`, the fuzz targets that require `--cfg=hashes_fuzz` +- `fuzz/fuzz-real-hashes`, the real-hashes fuzz targets, currently `chanmon_consistency_target` + ## How do I run fuzz tests locally? We support multiple fuzzing engines such as `honggfuzz`, `libFuzzer` and `AFL`. You typically won't @@ -47,34 +52,46 @@ cargo install --force cargo-fuzz To run fuzzing using `honggfuzz`, do ```shell +cd fuzz export CPU_COUNT=1 # replace as needed export HFUZZ_BUILD_ARGS="--features honggfuzz_fuzz" export HFUZZ_RUN_ARGS="-n $CPU_COUNT --exit_upon_crash" +export HFUZZ_WORKSPACE="./hfuzz_workspace" export TARGET="msg_ping_target" # replace with the target to be fuzzed -cargo hfuzz run $TARGET +export RUSTFLAGS="--cfg=fuzzing --cfg=secp256k1_fuzz --cfg=hashes_fuzz" +cargo hfuzz run --manifest-path fuzz-fake-hashes/Cargo.toml $TARGET ``` -(Or, for a prettier output, replace the last line with `cargo --color always hfuzz run $TARGET`.) +(For `fuzz-real-hashes`, use +`RUSTFLAGS="--cfg=fuzzing --cfg=secp256k1_fuzz" cargo hfuzz run --manifest-path fuzz-real-hashes/Cargo.toml chanmon_consistency_target`.) +For a prettier output, replace the last line with +`cargo --color always hfuzz run --manifest-path fuzz-fake-hashes/Cargo.toml $TARGET`. #### cargo-fuzz / libFuzzer To run fuzzing using `cargo-fuzz / libFuzzer`, run ```shell rustup install nightly # Note: libFuzzer requires a nightly version of rust. +cd fuzz export RUSTFLAGS="--cfg=fuzzing --cfg=secp256k1_fuzz --cfg=hashes_fuzz" -cargo +nightly fuzz run --features "libfuzzer_fuzz" msg_ping_target +cargo +nightly fuzz run --manifest-path fuzz-fake-hashes/Cargo.toml --features "libfuzzer_fuzz" msg_ping_target ``` Note: If you encounter a `SIGKILL` during run/build check for OOM in kernel logs and consider increasing RAM size for VM. +For `fuzz-real-hashes`, use +`RUSTFLAGS="--cfg=fuzzing --cfg=secp256k1_fuzz" cargo +nightly fuzz run --manifest-path fuzz-real-hashes/Cargo.toml --features "libfuzzer_fuzz" chanmon_consistency_target`. + ##### Fast builds for development The default build uses LTO and single codegen unit, which is slow. For faster iteration during development, use the `-D` (dev) flag: ```shell -cargo +nightly fuzz run --features "libfuzzer_fuzz" -D msg_ping_target +cd fuzz +RUSTFLAGS="--cfg=fuzzing --cfg=secp256k1_fuzz --cfg=hashes_fuzz" \ + cargo +nightly fuzz run --manifest-path fuzz-fake-hashes/Cargo.toml --features "libfuzzer_fuzz" -D msg_ping_target ``` The `-D` flag builds in development mode with faster compilation (still has optimizations via @@ -83,7 +100,9 @@ sanitizer instrumentation, but subsequent builds will be fast. If you wish to just generate fuzzing binary executables for `libFuzzer` and not run them: ```shell -cargo +nightly fuzz build --features "libfuzzer_fuzz" msg_ping_target +cd fuzz +RUSTFLAGS="--cfg=fuzzing --cfg=secp256k1_fuzz --cfg=hashes_fuzz" \ + cargo +nightly fuzz build --manifest-path fuzz-fake-hashes/Cargo.toml --features "libfuzzer_fuzz" msg_ping_target # Generates binary artifact in path ./target/aarch64-unknown-linux-gnu/release/msg_ping_target # Exact path depends on your system architecture. ``` @@ -93,7 +112,8 @@ You can upload the build artifact generated above to `ClusterFuzz` for distribut To see a list of available fuzzing targets, run: ```shell -ls ./src/bin/ +ls ./fuzz-fake-hashes/src/bin/ +ls ./fuzz-real-hashes/src/bin/ ``` ## A fuzz test failed, what do I do? @@ -134,7 +154,8 @@ mkdir -p ./test_cases/$TARGET echo $HEX | xxd -r -p > ./test_cases/$TARGET/any_filename_works export RUST_BACKTRACE=1 -cargo test +RUSTFLAGS="--cfg=fuzzing --cfg=secp256k1_fuzz --cfg=hashes_fuzz" \ + cargo test --manifest-path fuzz-fake-hashes/Cargo.toml --bin "${TARGET}_target" ``` Note that if the fuzz test failed locally, moving the offending run's trace @@ -151,7 +172,10 @@ Alternatively, you can use the `stdin_fuzz` feature to pipe the crash input dire creating test case files on disk: ```shell -echo -ne '\x2d\x31\x36\x38\x37\x34\x09\x01...' | cargo run --features stdin_fuzz --bin full_stack_target +cd fuzz +echo -ne '\x2d\x31\x36\x38\x37\x34\x09\x01...' | \ + RUSTFLAGS="--cfg=fuzzing --cfg=secp256k1_fuzz --cfg=hashes_fuzz" \ + cargo run --manifest-path fuzz-fake-hashes/Cargo.toml --features stdin_fuzz --bin full_stack_target ``` Panics will abort the process directly (the crate uses `panic = "abort"`), resulting in a @@ -171,10 +195,13 @@ file are `do_test`, `my_fuzzy_experiment_test`, and `my_fuzzy_experiment_run`. 3. Adjust the body (not the signature!) of `do_test` as necessary for the new fuzz test. -4. In `fuzz/src/bin/gen_target.sh`, add a line reading `GEN_TEST my_fuzzy_experiment` to the -first group of `GEN_TEST` lines (starting in line 9). +4. In `fuzz/src/bin/gen_target.sh`, add a line reading `GEN_FAKE_HASHES_TEST my_fuzzy_experiment` +to the appropriate target list. Use `GEN_REAL_HASHES_TEST` only for targets that must run without +`hashes_fuzz`. 5. If your test relies on a new local crate, add that crate as a dependency to `fuzz/Cargo.toml`. +If the dependency is only needed by a specific runner crate or fuzz engine setup, add it to the +matching target crate under `fuzz/fuzz-fake-hashes/Cargo.toml` or `fuzz/fuzz-real-hashes/Cargo.toml` instead. 6. In `fuzz/src/lib.rs`, add the line `pub mod my_fuzzy_experiment`. Additionally, if you added a new crate dependency, add the `extern crate […]` import line. diff --git a/fuzz/ci-fuzz.sh b/fuzz/ci-fuzz.sh index 47bf41ba620..3fc206bd0ee 100755 --- a/fuzz/ci-fuzz.sh +++ b/fuzz/ci-fuzz.sh @@ -8,16 +8,16 @@ rm msg_*.rs [ "$(git diff)" != "" ] && exit 1 popd pushd src/bin -rm *_target.rs +rm -f ../../fuzz-fake-hashes/src/bin/*_target.rs ../../fuzz-real-hashes/src/bin/*_target.rs ./gen_target.sh [ "$(git diff)" != "" ] && exit 1 popd -export RUSTFLAGS="--cfg=secp256k1_fuzz --cfg=hashes_fuzz" +export RUSTFLAGS="--cfg=fuzzing --cfg=secp256k1_fuzz --cfg=hashes_fuzz" mkdir -p hfuzz_workspace/full_stack_target/input pushd write-seeds -RUSTFLAGS="$RUSTFLAGS --cfg=fuzzing" cargo run ../hfuzz_workspace/full_stack_target/input +cargo run ../hfuzz_workspace/full_stack_target/input cargo clean popd @@ -27,57 +27,70 @@ cargo install --color always --force honggfuzz --no-default-features # compiler optimizations aren't necessary, so we turn off LTO sed -i 's/lto = true//' Cargo.toml -export HFUZZ_BUILD_ARGS="--features honggfuzz_fuzz" - -cargo --color always hfuzz build -j8 - SUMMARY="" check_crash() { - local FILE=$1 - if [ -f "hfuzz_workspace/$FILE/HONGGFUZZ.REPORT.TXT" ]; then - cat "hfuzz_workspace/$FILE/HONGGFUZZ.REPORT.TXT" - for CASE in "hfuzz_workspace/$FILE"/SIG*; do + local WORKSPACE_DIR=$1 + local FILE=$2 + if [ -f "$WORKSPACE_DIR/$FILE/HONGGFUZZ.REPORT.TXT" ]; then + cat "$WORKSPACE_DIR/$FILE/HONGGFUZZ.REPORT.TXT" + for CASE in "$WORKSPACE_DIR/$FILE"/SIG*; do cat "$CASE" | xxd -p done exit 1 fi } -for TARGET in src/bin/*.rs; do - FILENAME=$(basename $TARGET) - FILE="${FILENAME%.*}" - CORPUS_DIR="hfuzz_workspace/$FILE/input" - CORPUS_COUNT=$(find "$CORPUS_DIR" -type f 2>/dev/null | wc -l) - # Run 8x the corpus size plus a baseline, ensuring full corpus replay - # with room for new mutations. The 10-minute hard cap (--run_time 600) - # prevents slow-per-iteration targets from running too long. - ITERATIONS=$((CORPUS_COUNT * 8 + 1000)) - HFUZZ_RUN_ARGS="--exit_upon_crash -q -n8 -t 3 -N $ITERATIONS --run_time 600" - if [ "$FILE" = "chanmon_consistency_target" -o "$FILE" = "fs_store_target" ]; then - HFUZZ_RUN_ARGS="$HFUZZ_RUN_ARGS -F 64" - fi - export HFUZZ_RUN_ARGS - FUZZ_START=$(date +%s) - cargo --color always hfuzz run $FILE - FUZZ_END=$(date +%s) - FUZZ_TIME=$((FUZZ_END - FUZZ_START)) - FUZZ_CORPUS_COUNT=$(find "$CORPUS_DIR" -type f 2>/dev/null | wc -l) - check_crash "$FILE" - if [ "$GITHUB_REF" = "refs/heads/main" ] || [ "$FUZZ_MINIMIZE" = "true" ]; then - HFUZZ_RUN_ARGS="-M -q -n8 -t 3" +run_targets() { + local CRATE_DIR=$1 + local TARGET_RUSTFLAGS=$2 + + pushd "$CRATE_DIR" + export HFUZZ_WORKSPACE="../hfuzz_workspace" + export HFUZZ_BUILD_ARGS="--features honggfuzz_fuzz" + export RUSTFLAGS="$TARGET_RUSTFLAGS" + cargo --color always hfuzz build -j8 + + for TARGET in src/bin/*.rs; do + FILENAME=$(basename "$TARGET") + FILE="${FILENAME%.*}" + CORPUS_DIR="$HFUZZ_WORKSPACE/$FILE/input" + CORPUS_COUNT=$(find "$CORPUS_DIR" -type f 2>/dev/null | wc -l) + # Run 8x the corpus size plus a baseline, ensuring full corpus replay + # with room for new mutations. The 10-minute hard cap (--run_time 600) + # prevents slow-per-iteration targets from running too long. + ITERATIONS=$((CORPUS_COUNT * 8 + 1000)) + HFUZZ_RUN_ARGS="--exit_upon_crash -q -n8 -t 3 -N $ITERATIONS --run_time 600" + if [ "$FILE" = "chanmon_consistency_target" -o "$FILE" = "fs_store_target" ]; then + HFUZZ_RUN_ARGS="$HFUZZ_RUN_ARGS -F 64" + fi export HFUZZ_RUN_ARGS - MIN_START=$(date +%s) - cargo --color always hfuzz run $FILE - MIN_END=$(date +%s) - MIN_TIME=$((MIN_END - MIN_START)) - MIN_CORPUS_COUNT=$(find "$CORPUS_DIR" -type f 2>/dev/null | wc -l) - check_crash "$FILE" - SUMMARY="${SUMMARY}${FILE}|${ITERATIONS}|${CORPUS_COUNT}|${FUZZ_CORPUS_COUNT}|${FUZZ_TIME}|${MIN_CORPUS_COUNT}|${MIN_TIME}\n" - else - SUMMARY="${SUMMARY}${FILE}|${ITERATIONS}|${CORPUS_COUNT}|${FUZZ_CORPUS_COUNT}|${FUZZ_TIME}|-|-\n" - fi -done + FUZZ_START=$(date +%s) + cargo --color always hfuzz run "$FILE" + FUZZ_END=$(date +%s) + FUZZ_TIME=$((FUZZ_END - FUZZ_START)) + FUZZ_CORPUS_COUNT=$(find "$CORPUS_DIR" -type f 2>/dev/null | wc -l) + check_crash "$HFUZZ_WORKSPACE" "$FILE" + if [ "$GITHUB_REF" = "refs/heads/main" ] || [ "$FUZZ_MINIMIZE" = "true" ]; then + HFUZZ_RUN_ARGS="-M -q -n8 -t 3" + export HFUZZ_RUN_ARGS + MIN_START=$(date +%s) + cargo --color always hfuzz run "$FILE" + MIN_END=$(date +%s) + MIN_TIME=$((MIN_END - MIN_START)) + MIN_CORPUS_COUNT=$(find "$CORPUS_DIR" -type f 2>/dev/null | wc -l) + check_crash "$HFUZZ_WORKSPACE" "$FILE" + SUMMARY="${SUMMARY}${FILE}|${ITERATIONS}|${CORPUS_COUNT}|${FUZZ_CORPUS_COUNT}|${FUZZ_TIME}|${MIN_CORPUS_COUNT}|${MIN_TIME}\n" + else + SUMMARY="${SUMMARY}${FILE}|${ITERATIONS}|${CORPUS_COUNT}|${FUZZ_CORPUS_COUNT}|${FUZZ_TIME}|-|-\n" + fi + done + + popd +} + +run_targets fuzz-fake-hashes "--cfg=fuzzing --cfg=secp256k1_fuzz --cfg=hashes_fuzz" +run_targets fuzz-real-hashes "--cfg=fuzzing --cfg=secp256k1_fuzz" fmt_time() { local secs=$1 diff --git a/fuzz/fuzz-fake-hashes/Cargo.toml b/fuzz/fuzz-fake-hashes/Cargo.toml new file mode 100644 index 00000000000..d027540a056 --- /dev/null +++ b/fuzz/fuzz-fake-hashes/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "lightning-fuzz-fake-hashes" +version = "0.0.1" +authors = ["Automatically generated"] +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[features] +afl_fuzz = ["afl"] +honggfuzz_fuzz = ["honggfuzz"] +libfuzzer_fuzz = ["libfuzzer-sys"] +stdin_fuzz = [] + +[dependencies] +lightning-fuzz = { path = ".." } + +afl = { version = "0.12", optional = true } +honggfuzz = { version = "0.5", optional = true, default-features = false } +libfuzzer-sys = { version = "0.4", optional = true } + +[lints.rust.unexpected_cfgs] +level = "forbid" +# When adding a new cfg attribute, ensure that it is added to this list. +check-cfg = [ + "cfg(fuzzing)", + "cfg(secp256k1_fuzz)", + "cfg(hashes_fuzz)", +] diff --git a/fuzz/src/bin/base32_target.rs b/fuzz/fuzz-fake-hashes/src/bin/base32_target.rs similarity index 95% rename from fuzz/src/bin/base32_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/base32_target.rs index e79e6db7380..e3cd1a66dd2 100644 --- a/fuzz/src/bin/base32_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/base32_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - base32_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + base32_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/base32") { + if let Ok(tests) = fs::read_dir("../test_cases/base32") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/bech32_parse_target.rs b/fuzz/fuzz-fake-hashes/src/bin/bech32_parse_target.rs similarity index 95% rename from fuzz/src/bin/bech32_parse_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/bech32_parse_target.rs index f9493bb1bc1..226ff19c472 100644 --- a/fuzz/src/bin/bech32_parse_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/bech32_parse_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - bech32_parse_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + bech32_parse_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/bech32_parse") { + if let Ok(tests) = fs::read_dir("../test_cases/bech32_parse") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/bolt11_deser_target.rs b/fuzz/fuzz-fake-hashes/src/bin/bolt11_deser_target.rs similarity index 95% rename from fuzz/src/bin/bolt11_deser_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/bolt11_deser_target.rs index 28b1e2db679..befa78fc105 100644 --- a/fuzz/src/bin/bolt11_deser_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/bolt11_deser_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - bolt11_deser_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + bolt11_deser_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/bolt11_deser") { + if let Ok(tests) = fs::read_dir("../test_cases/bolt11_deser") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/chanmon_deser_target.rs b/fuzz/fuzz-fake-hashes/src/bin/chanmon_deser_target.rs similarity index 95% rename from fuzz/src/bin/chanmon_deser_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/chanmon_deser_target.rs index d3cf30b86e3..259f9d36ad2 100644 --- a/fuzz/src/bin/chanmon_deser_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/chanmon_deser_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - chanmon_deser_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + chanmon_deser_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/chanmon_deser") { + if let Ok(tests) = fs::read_dir("../test_cases/chanmon_deser") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/feature_flags_target.rs b/fuzz/fuzz-fake-hashes/src/bin/feature_flags_target.rs similarity index 95% rename from fuzz/src/bin/feature_flags_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/feature_flags_target.rs index b1f35f8820f..d54bba994e8 100644 --- a/fuzz/src/bin/feature_flags_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/feature_flags_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - feature_flags_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + feature_flags_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/feature_flags") { + if let Ok(tests) = fs::read_dir("../test_cases/feature_flags") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/fromstr_to_netaddress_target.rs b/fuzz/fuzz-fake-hashes/src/bin/fromstr_to_netaddress_target.rs similarity index 94% rename from fuzz/src/bin/fromstr_to_netaddress_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/fromstr_to_netaddress_target.rs index 8f3e5c3dc7f..94cedd91157 100644 --- a/fuzz/src/bin/fromstr_to_netaddress_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/fromstr_to_netaddress_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - fromstr_to_netaddress_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + fromstr_to_netaddress_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/fromstr_to_netaddress") { + if let Ok(tests) = fs::read_dir("../test_cases/fromstr_to_netaddress") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/fs_store_target.rs b/fuzz/fuzz-fake-hashes/src/bin/fs_store_target.rs similarity index 95% rename from fuzz/src/bin/fs_store_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/fs_store_target.rs index 8d84aad7b6b..e34cab13def 100644 --- a/fuzz/src/bin/fs_store_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/fs_store_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - fs_store_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + fs_store_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/fs_store") { + if let Ok(tests) = fs::read_dir("../test_cases/fs_store") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/full_stack_target.rs b/fuzz/fuzz-fake-hashes/src/bin/full_stack_target.rs similarity index 95% rename from fuzz/src/bin/full_stack_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/full_stack_target.rs index c1f20b10af4..81a49776b4b 100644 --- a/fuzz/src/bin/full_stack_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/full_stack_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - full_stack_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + full_stack_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/full_stack") { + if let Ok(tests) = fs::read_dir("../test_cases/full_stack") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/gossip_discovery_target.rs b/fuzz/fuzz-fake-hashes/src/bin/gossip_discovery_target.rs similarity index 95% rename from fuzz/src/bin/gossip_discovery_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/gossip_discovery_target.rs index 960ba80ec8c..470ad17fe26 100644 --- a/fuzz/src/bin/gossip_discovery_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/gossip_discovery_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - gossip_discovery_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + gossip_discovery_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/gossip_discovery") { + if let Ok(tests) = fs::read_dir("../test_cases/gossip_discovery") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/indexedmap_target.rs b/fuzz/fuzz-fake-hashes/src/bin/indexedmap_target.rs similarity index 95% rename from fuzz/src/bin/indexedmap_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/indexedmap_target.rs index 3bc4390fee4..e8d7626a238 100644 --- a/fuzz/src/bin/indexedmap_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/indexedmap_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - indexedmap_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + indexedmap_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/indexedmap") { + if let Ok(tests) = fs::read_dir("../test_cases/indexedmap") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/invoice_deser_target.rs b/fuzz/fuzz-fake-hashes/src/bin/invoice_deser_target.rs similarity index 95% rename from fuzz/src/bin/invoice_deser_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/invoice_deser_target.rs index 44bf1851a40..c1338f62e0e 100644 --- a/fuzz/src/bin/invoice_deser_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/invoice_deser_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - invoice_deser_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + invoice_deser_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/invoice_deser") { + if let Ok(tests) = fs::read_dir("../test_cases/invoice_deser") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/invoice_request_deser_target.rs b/fuzz/fuzz-fake-hashes/src/bin/invoice_request_deser_target.rs similarity index 94% rename from fuzz/src/bin/invoice_request_deser_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/invoice_request_deser_target.rs index 06d8f87fa55..2198b64b207 100644 --- a/fuzz/src/bin/invoice_request_deser_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/invoice_request_deser_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - invoice_request_deser_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + invoice_request_deser_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/invoice_request_deser") { + if let Ok(tests) = fs::read_dir("../test_cases/invoice_request_deser") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/lsps_message_target.rs b/fuzz/fuzz-fake-hashes/src/bin/lsps_message_target.rs similarity index 95% rename from fuzz/src/bin/lsps_message_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/lsps_message_target.rs index 37e6f103fb4..68e1c8b0e06 100644 --- a/fuzz/src/bin/lsps_message_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/lsps_message_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - lsps_message_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + lsps_message_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/lsps_message") { + if let Ok(tests) = fs::read_dir("../test_cases/lsps_message") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_accept_channel_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_accept_channel_target.rs similarity index 95% rename from fuzz/src/bin/msg_accept_channel_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_accept_channel_target.rs index ee08a5fc344..798e2d9e5aa 100644 --- a/fuzz/src/bin/msg_accept_channel_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_accept_channel_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_accept_channel_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_accept_channel_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_accept_channel") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_accept_channel") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_accept_channel_v2_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_accept_channel_v2_target.rs similarity index 94% rename from fuzz/src/bin/msg_accept_channel_v2_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_accept_channel_v2_target.rs index 2903e111f56..eff73d11ded 100644 --- a/fuzz/src/bin/msg_accept_channel_v2_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_accept_channel_v2_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_accept_channel_v2_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_accept_channel_v2_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_accept_channel_v2") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_accept_channel_v2") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_announcement_signatures_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_announcement_signatures_target.rs similarity index 94% rename from fuzz/src/bin/msg_announcement_signatures_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_announcement_signatures_target.rs index 064880abc18..09b76396873 100644 --- a/fuzz/src/bin/msg_announcement_signatures_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_announcement_signatures_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_announcement_signatures_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_announcement_signatures_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_announcement_signatures") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_announcement_signatures") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_blinded_message_path_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_blinded_message_path_target.rs similarity index 94% rename from fuzz/src/bin/msg_blinded_message_path_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_blinded_message_path_target.rs index 277e04c9656..92c0976dc79 100644 --- a/fuzz/src/bin/msg_blinded_message_path_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_blinded_message_path_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_blinded_message_path_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_blinded_message_path_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_blinded_message_path") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_blinded_message_path") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_channel_announcement_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_channel_announcement_target.rs similarity index 94% rename from fuzz/src/bin/msg_channel_announcement_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_channel_announcement_target.rs index 42e72d54b72..482dbbc4345 100644 --- a/fuzz/src/bin/msg_channel_announcement_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_channel_announcement_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_channel_announcement_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_channel_announcement_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_channel_announcement") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_channel_announcement") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_channel_details_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_channel_details_target.rs similarity index 94% rename from fuzz/src/bin/msg_channel_details_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_channel_details_target.rs index a03a7a44920..04af6755917 100644 --- a/fuzz/src/bin/msg_channel_details_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_channel_details_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_channel_details_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_channel_details_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_channel_details") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_channel_details") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_channel_ready_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_channel_ready_target.rs similarity index 95% rename from fuzz/src/bin/msg_channel_ready_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_channel_ready_target.rs index a0457815036..34511509f39 100644 --- a/fuzz/src/bin/msg_channel_ready_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_channel_ready_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_channel_ready_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_channel_ready_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_channel_ready") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_channel_ready") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_channel_reestablish_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_channel_reestablish_target.rs similarity index 94% rename from fuzz/src/bin/msg_channel_reestablish_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_channel_reestablish_target.rs index b5449a90e37..0541cedafe2 100644 --- a/fuzz/src/bin/msg_channel_reestablish_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_channel_reestablish_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_channel_reestablish_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_channel_reestablish_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_channel_reestablish") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_channel_reestablish") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_channel_update_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_channel_update_target.rs similarity index 95% rename from fuzz/src/bin/msg_channel_update_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_channel_update_target.rs index 9feb6e6c6b4..7d08ee24005 100644 --- a/fuzz/src/bin/msg_channel_update_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_channel_update_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_channel_update_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_channel_update_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_channel_update") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_channel_update") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_closing_complete_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_closing_complete_target.rs similarity index 94% rename from fuzz/src/bin/msg_closing_complete_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_closing_complete_target.rs index 22dd97c79c9..7bcb76d2fbd 100644 --- a/fuzz/src/bin/msg_closing_complete_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_closing_complete_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_closing_complete_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_closing_complete_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_closing_complete") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_closing_complete") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_closing_sig_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_closing_sig_target.rs similarity index 95% rename from fuzz/src/bin/msg_closing_sig_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_closing_sig_target.rs index 26058a5277d..54669e259c3 100644 --- a/fuzz/src/bin/msg_closing_sig_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_closing_sig_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_closing_sig_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_closing_sig_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_closing_sig") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_closing_sig") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_closing_signed_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_closing_signed_target.rs similarity index 95% rename from fuzz/src/bin/msg_closing_signed_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_closing_signed_target.rs index 94408bc2ba9..f5813a7919d 100644 --- a/fuzz/src/bin/msg_closing_signed_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_closing_signed_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_closing_signed_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_closing_signed_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_closing_signed") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_closing_signed") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_commitment_signed_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_commitment_signed_target.rs similarity index 94% rename from fuzz/src/bin/msg_commitment_signed_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_commitment_signed_target.rs index e8987848417..a62449b1673 100644 --- a/fuzz/src/bin/msg_commitment_signed_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_commitment_signed_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_commitment_signed_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_commitment_signed_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_commitment_signed") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_commitment_signed") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_decoded_onion_error_packet_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_decoded_onion_error_packet_target.rs similarity index 94% rename from fuzz/src/bin/msg_decoded_onion_error_packet_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_decoded_onion_error_packet_target.rs index 47d8970b453..75e37116d79 100644 --- a/fuzz/src/bin/msg_decoded_onion_error_packet_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_decoded_onion_error_packet_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_decoded_onion_error_packet_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_decoded_onion_error_packet_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_decoded_onion_error_packet") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_decoded_onion_error_packet") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_error_message_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_error_message_target.rs similarity index 95% rename from fuzz/src/bin/msg_error_message_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_error_message_target.rs index ee3904a724e..23c9524478d 100644 --- a/fuzz/src/bin/msg_error_message_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_error_message_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_error_message_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_error_message_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_error_message") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_error_message") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_funding_created_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_funding_created_target.rs similarity index 94% rename from fuzz/src/bin/msg_funding_created_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_funding_created_target.rs index 028aa17ad8a..c423e6e9c24 100644 --- a/fuzz/src/bin/msg_funding_created_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_funding_created_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_funding_created_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_funding_created_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_funding_created") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_funding_created") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_funding_signed_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_funding_signed_target.rs similarity index 95% rename from fuzz/src/bin/msg_funding_signed_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_funding_signed_target.rs index 4894c66df0b..de10f0e71dc 100644 --- a/fuzz/src/bin/msg_funding_signed_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_funding_signed_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_funding_signed_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_funding_signed_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_funding_signed") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_funding_signed") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_gossip_timestamp_filter_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_gossip_timestamp_filter_target.rs similarity index 94% rename from fuzz/src/bin/msg_gossip_timestamp_filter_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_gossip_timestamp_filter_target.rs index 6da383b2e6f..cef5bc576c2 100644 --- a/fuzz/src/bin/msg_gossip_timestamp_filter_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_gossip_timestamp_filter_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_gossip_timestamp_filter_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_gossip_timestamp_filter_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_gossip_timestamp_filter") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_gossip_timestamp_filter") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_init_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_init_target.rs similarity index 95% rename from fuzz/src/bin/msg_init_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_init_target.rs index f1d17c99289..7e51e6e63e5 100644 --- a/fuzz/src/bin/msg_init_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_init_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_init_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_init_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_init") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_init") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_node_announcement_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_node_announcement_target.rs similarity index 94% rename from fuzz/src/bin/msg_node_announcement_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_node_announcement_target.rs index b0615f3c5e5..c7aaecb644a 100644 --- a/fuzz/src/bin/msg_node_announcement_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_node_announcement_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_node_announcement_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_node_announcement_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_node_announcement") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_node_announcement") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_open_channel_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_open_channel_target.rs similarity index 95% rename from fuzz/src/bin/msg_open_channel_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_open_channel_target.rs index b3dbf388c08..bb49be7d994 100644 --- a/fuzz/src/bin/msg_open_channel_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_open_channel_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_open_channel_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_open_channel_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_open_channel") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_open_channel") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_open_channel_v2_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_open_channel_v2_target.rs similarity index 94% rename from fuzz/src/bin/msg_open_channel_v2_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_open_channel_v2_target.rs index 0df11adf32e..a6d45dc3a45 100644 --- a/fuzz/src/bin/msg_open_channel_v2_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_open_channel_v2_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_open_channel_v2_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_open_channel_v2_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_open_channel_v2") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_open_channel_v2") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_ping_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_ping_target.rs similarity index 95% rename from fuzz/src/bin/msg_ping_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_ping_target.rs index 48f855985de..70bb751c594 100644 --- a/fuzz/src/bin/msg_ping_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_ping_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_ping_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_ping_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_ping") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_ping") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_pong_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_pong_target.rs similarity index 95% rename from fuzz/src/bin/msg_pong_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_pong_target.rs index 434e9cfe310..74df6d86474 100644 --- a/fuzz/src/bin/msg_pong_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_pong_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_pong_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_pong_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_pong") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_pong") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_query_channel_range_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_query_channel_range_target.rs similarity index 94% rename from fuzz/src/bin/msg_query_channel_range_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_query_channel_range_target.rs index cb87260e1ef..e497491083f 100644 --- a/fuzz/src/bin/msg_query_channel_range_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_query_channel_range_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_query_channel_range_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_query_channel_range_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_query_channel_range") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_query_channel_range") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_query_short_channel_ids_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_query_short_channel_ids_target.rs similarity index 94% rename from fuzz/src/bin/msg_query_short_channel_ids_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_query_short_channel_ids_target.rs index bc286a7e523..31169f9e665 100644 --- a/fuzz/src/bin/msg_query_short_channel_ids_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_query_short_channel_ids_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_query_short_channel_ids_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_query_short_channel_ids_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_query_short_channel_ids") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_query_short_channel_ids") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_reply_channel_range_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_reply_channel_range_target.rs similarity index 94% rename from fuzz/src/bin/msg_reply_channel_range_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_reply_channel_range_target.rs index c7df076c6c6..a0aaadf321d 100644 --- a/fuzz/src/bin/msg_reply_channel_range_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_reply_channel_range_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_reply_channel_range_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_reply_channel_range_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_reply_channel_range") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_reply_channel_range") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_reply_short_channel_ids_end_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_reply_short_channel_ids_end_target.rs similarity index 94% rename from fuzz/src/bin/msg_reply_short_channel_ids_end_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_reply_short_channel_ids_end_target.rs index 2c73d866bd9..8931538e4f5 100644 --- a/fuzz/src/bin/msg_reply_short_channel_ids_end_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_reply_short_channel_ids_end_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_reply_short_channel_ids_end_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_reply_short_channel_ids_end_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_reply_short_channel_ids_end") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_reply_short_channel_ids_end") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_revoke_and_ack_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_revoke_and_ack_target.rs similarity index 95% rename from fuzz/src/bin/msg_revoke_and_ack_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_revoke_and_ack_target.rs index 6379d39591f..6ed40d3ab91 100644 --- a/fuzz/src/bin/msg_revoke_and_ack_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_revoke_and_ack_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_revoke_and_ack_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_revoke_and_ack_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_revoke_and_ack") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_revoke_and_ack") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_shutdown_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_shutdown_target.rs similarity index 95% rename from fuzz/src/bin/msg_shutdown_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_shutdown_target.rs index 6bf0409b7b5..a731a1dd91f 100644 --- a/fuzz/src/bin/msg_shutdown_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_shutdown_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_shutdown_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_shutdown_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_shutdown") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_shutdown") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_splice_ack_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_splice_ack_target.rs similarity index 95% rename from fuzz/src/bin/msg_splice_ack_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_splice_ack_target.rs index 96f373d5a1c..20625fc759c 100644 --- a/fuzz/src/bin/msg_splice_ack_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_splice_ack_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_splice_ack_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_splice_ack_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_splice_ack") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_splice_ack") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_splice_init_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_splice_init_target.rs similarity index 95% rename from fuzz/src/bin/msg_splice_init_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_splice_init_target.rs index 73d4319c44a..b3d30a660a1 100644 --- a/fuzz/src/bin/msg_splice_init_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_splice_init_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_splice_init_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_splice_init_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_splice_init") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_splice_init") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_splice_locked_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_splice_locked_target.rs similarity index 95% rename from fuzz/src/bin/msg_splice_locked_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_splice_locked_target.rs index 9210113a0c8..deb57b61974 100644 --- a/fuzz/src/bin/msg_splice_locked_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_splice_locked_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_splice_locked_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_splice_locked_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_splice_locked") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_splice_locked") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_stfu_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_stfu_target.rs similarity index 95% rename from fuzz/src/bin/msg_stfu_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_stfu_target.rs index d00536c7bcd..de3a64f542b 100644 --- a/fuzz/src/bin/msg_stfu_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_stfu_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_stfu_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_stfu_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_stfu") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_stfu") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_tx_abort_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_tx_abort_target.rs similarity index 95% rename from fuzz/src/bin/msg_tx_abort_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_tx_abort_target.rs index 8f216b46e63..0b335c23b18 100644 --- a/fuzz/src/bin/msg_tx_abort_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_tx_abort_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_tx_abort_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_tx_abort_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_tx_abort") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_tx_abort") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_tx_ack_rbf_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_tx_ack_rbf_target.rs similarity index 95% rename from fuzz/src/bin/msg_tx_ack_rbf_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_tx_ack_rbf_target.rs index 90b34c7f93f..d69077c9075 100644 --- a/fuzz/src/bin/msg_tx_ack_rbf_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_tx_ack_rbf_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_tx_ack_rbf_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_tx_ack_rbf_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_tx_ack_rbf") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_tx_ack_rbf") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_tx_add_input_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_tx_add_input_target.rs similarity index 95% rename from fuzz/src/bin/msg_tx_add_input_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_tx_add_input_target.rs index ce9700bd344..8dff0a621c9 100644 --- a/fuzz/src/bin/msg_tx_add_input_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_tx_add_input_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_tx_add_input_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_tx_add_input_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_tx_add_input") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_tx_add_input") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_tx_add_output_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_tx_add_output_target.rs similarity index 95% rename from fuzz/src/bin/msg_tx_add_output_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_tx_add_output_target.rs index 02682194e13..f6808399aba 100644 --- a/fuzz/src/bin/msg_tx_add_output_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_tx_add_output_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_tx_add_output_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_tx_add_output_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_tx_add_output") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_tx_add_output") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_tx_complete_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_tx_complete_target.rs similarity index 95% rename from fuzz/src/bin/msg_tx_complete_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_tx_complete_target.rs index 48864f053c8..2edccfbf690 100644 --- a/fuzz/src/bin/msg_tx_complete_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_tx_complete_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_tx_complete_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_tx_complete_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_tx_complete") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_tx_complete") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_tx_init_rbf_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_tx_init_rbf_target.rs similarity index 95% rename from fuzz/src/bin/msg_tx_init_rbf_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_tx_init_rbf_target.rs index a8b613cdfca..80acf0f11c8 100644 --- a/fuzz/src/bin/msg_tx_init_rbf_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_tx_init_rbf_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_tx_init_rbf_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_tx_init_rbf_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_tx_init_rbf") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_tx_init_rbf") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_tx_remove_input_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_tx_remove_input_target.rs similarity index 94% rename from fuzz/src/bin/msg_tx_remove_input_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_tx_remove_input_target.rs index 1e46c547dbf..b1555a2412a 100644 --- a/fuzz/src/bin/msg_tx_remove_input_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_tx_remove_input_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_tx_remove_input_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_tx_remove_input_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_tx_remove_input") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_tx_remove_input") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_tx_remove_output_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_tx_remove_output_target.rs similarity index 94% rename from fuzz/src/bin/msg_tx_remove_output_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_tx_remove_output_target.rs index 3a9c178e75f..a8e5b20d06d 100644 --- a/fuzz/src/bin/msg_tx_remove_output_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_tx_remove_output_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_tx_remove_output_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_tx_remove_output_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_tx_remove_output") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_tx_remove_output") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_tx_signatures_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_tx_signatures_target.rs similarity index 95% rename from fuzz/src/bin/msg_tx_signatures_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_tx_signatures_target.rs index 77f34cc1f6a..2a1fbf9d16e 100644 --- a/fuzz/src/bin/msg_tx_signatures_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_tx_signatures_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_tx_signatures_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_tx_signatures_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_tx_signatures") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_tx_signatures") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_update_add_htlc_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_update_add_htlc_target.rs similarity index 94% rename from fuzz/src/bin/msg_update_add_htlc_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_update_add_htlc_target.rs index 3ff5cf83dbe..d3b45d589eb 100644 --- a/fuzz/src/bin/msg_update_add_htlc_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_update_add_htlc_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_update_add_htlc_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_update_add_htlc_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_update_add_htlc") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_update_add_htlc") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_update_fail_htlc_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_update_fail_htlc_target.rs similarity index 94% rename from fuzz/src/bin/msg_update_fail_htlc_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_update_fail_htlc_target.rs index 5b8a7e55dcb..bec5bc9e331 100644 --- a/fuzz/src/bin/msg_update_fail_htlc_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_update_fail_htlc_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_update_fail_htlc_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_update_fail_htlc_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_update_fail_htlc") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_update_fail_htlc") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_update_fail_malformed_htlc_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_update_fail_malformed_htlc_target.rs similarity index 94% rename from fuzz/src/bin/msg_update_fail_malformed_htlc_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_update_fail_malformed_htlc_target.rs index e3e8918e492..190412bc0a7 100644 --- a/fuzz/src/bin/msg_update_fail_malformed_htlc_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_update_fail_malformed_htlc_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_update_fail_malformed_htlc_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_update_fail_malformed_htlc_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_update_fail_malformed_htlc") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_update_fail_malformed_htlc") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_update_fee_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_update_fee_target.rs similarity index 95% rename from fuzz/src/bin/msg_update_fee_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_update_fee_target.rs index 98e51181c79..386db47ae9f 100644 --- a/fuzz/src/bin/msg_update_fee_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_update_fee_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_update_fee_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_update_fee_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_update_fee") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_update_fee") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/msg_update_fulfill_htlc_target.rs b/fuzz/fuzz-fake-hashes/src/bin/msg_update_fulfill_htlc_target.rs similarity index 94% rename from fuzz/src/bin/msg_update_fulfill_htlc_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/msg_update_fulfill_htlc_target.rs index cb156448e13..ab49c21043e 100644 --- a/fuzz/src/bin/msg_update_fulfill_htlc_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/msg_update_fulfill_htlc_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - msg_update_fulfill_htlc_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + msg_update_fulfill_htlc_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/msg_update_fulfill_htlc") { + if let Ok(tests) = fs::read_dir("../test_cases/msg_update_fulfill_htlc") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/offer_deser_target.rs b/fuzz/fuzz-fake-hashes/src/bin/offer_deser_target.rs similarity index 95% rename from fuzz/src/bin/offer_deser_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/offer_deser_target.rs index c4a03f628b3..25eda5618f0 100644 --- a/fuzz/src/bin/offer_deser_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/offer_deser_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - offer_deser_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + offer_deser_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/offer_deser") { + if let Ok(tests) = fs::read_dir("../test_cases/offer_deser") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/onion_hop_data_target.rs b/fuzz/fuzz-fake-hashes/src/bin/onion_hop_data_target.rs similarity index 95% rename from fuzz/src/bin/onion_hop_data_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/onion_hop_data_target.rs index 3b9b55bbfa9..05ce4d76aeb 100644 --- a/fuzz/src/bin/onion_hop_data_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/onion_hop_data_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - onion_hop_data_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + onion_hop_data_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/onion_hop_data") { + if let Ok(tests) = fs::read_dir("../test_cases/onion_hop_data") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/onion_message_target.rs b/fuzz/fuzz-fake-hashes/src/bin/onion_message_target.rs similarity index 95% rename from fuzz/src/bin/onion_message_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/onion_message_target.rs index bb343e9de83..f5a0eb60171 100644 --- a/fuzz/src/bin/onion_message_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/onion_message_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - onion_message_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + onion_message_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/onion_message") { + if let Ok(tests) = fs::read_dir("../test_cases/onion_message") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/peer_crypt_target.rs b/fuzz/fuzz-fake-hashes/src/bin/peer_crypt_target.rs similarity index 95% rename from fuzz/src/bin/peer_crypt_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/peer_crypt_target.rs index c68111deb06..3095f2a870c 100644 --- a/fuzz/src/bin/peer_crypt_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/peer_crypt_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - peer_crypt_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + peer_crypt_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/peer_crypt") { + if let Ok(tests) = fs::read_dir("../test_cases/peer_crypt") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/process_network_graph_target.rs b/fuzz/fuzz-fake-hashes/src/bin/process_network_graph_target.rs similarity index 94% rename from fuzz/src/bin/process_network_graph_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/process_network_graph_target.rs index 7da2aafe3c8..36ea42bcb6a 100644 --- a/fuzz/src/bin/process_network_graph_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/process_network_graph_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - process_network_graph_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + process_network_graph_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/process_network_graph") { + if let Ok(tests) = fs::read_dir("../test_cases/process_network_graph") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/process_onion_failure_target.rs b/fuzz/fuzz-fake-hashes/src/bin/process_onion_failure_target.rs similarity index 94% rename from fuzz/src/bin/process_onion_failure_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/process_onion_failure_target.rs index 9e1cc8aa6d0..1d6c64c5863 100644 --- a/fuzz/src/bin/process_onion_failure_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/process_onion_failure_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - process_onion_failure_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + process_onion_failure_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/process_onion_failure") { + if let Ok(tests) = fs::read_dir("../test_cases/process_onion_failure") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/refund_deser_target.rs b/fuzz/fuzz-fake-hashes/src/bin/refund_deser_target.rs similarity index 95% rename from fuzz/src/bin/refund_deser_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/refund_deser_target.rs index 13837d2be73..a5295b8d793 100644 --- a/fuzz/src/bin/refund_deser_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/refund_deser_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - refund_deser_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + refund_deser_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/refund_deser") { + if let Ok(tests) = fs::read_dir("../test_cases/refund_deser") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/router_target.rs b/fuzz/fuzz-fake-hashes/src/bin/router_target.rs similarity index 95% rename from fuzz/src/bin/router_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/router_target.rs index 52a8c3408ff..ecf6dbe9b57 100644 --- a/fuzz/src/bin/router_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/router_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - router_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + router_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/router") { + if let Ok(tests) = fs::read_dir("../test_cases/router") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/static_invoice_deser_target.rs b/fuzz/fuzz-fake-hashes/src/bin/static_invoice_deser_target.rs similarity index 94% rename from fuzz/src/bin/static_invoice_deser_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/static_invoice_deser_target.rs index 477f7869e7f..787817de00e 100644 --- a/fuzz/src/bin/static_invoice_deser_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/static_invoice_deser_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - static_invoice_deser_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + static_invoice_deser_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/static_invoice_deser") { + if let Ok(tests) = fs::read_dir("../test_cases/static_invoice_deser") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/zbase32_target.rs b/fuzz/fuzz-fake-hashes/src/bin/zbase32_target.rs similarity index 95% rename from fuzz/src/bin/zbase32_target.rs rename to fuzz/fuzz-fake-hashes/src/bin/zbase32_target.rs index 68c8cf3e19c..1007df19acf 100644 --- a/fuzz/src/bin/zbase32_target.rs +++ b/fuzz/fuzz-fake-hashes/src/bin/zbase32_target.rs @@ -17,7 +17,7 @@ compile_error!("Fuzz targets need cfg=fuzzing"); #[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +compile_error!("Fuzz target does not support cfg(not(hashes_fuzz))"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - zbase32_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + zbase32_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/zbase32") { + if let Ok(tests) = fs::read_dir("../test_cases/zbase32") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/fuzz-real-hashes/Cargo.toml b/fuzz/fuzz-real-hashes/Cargo.toml new file mode 100644 index 00000000000..a6d77d28137 --- /dev/null +++ b/fuzz/fuzz-real-hashes/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "lightning-fuzz-real-hashes" +version = "0.0.1" +authors = ["Automatically generated"] +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[features] +afl_fuzz = ["afl"] +honggfuzz_fuzz = ["honggfuzz"] +libfuzzer_fuzz = ["libfuzzer-sys"] +stdin_fuzz = [] + +[dependencies] +lightning-fuzz = { path = ".." } + +afl = { version = "0.12", optional = true } +honggfuzz = { version = "0.5", optional = true, default-features = false } +libfuzzer-sys = { version = "0.4", optional = true } + +[lints.rust.unexpected_cfgs] +level = "forbid" +# When adding a new cfg attribute, ensure that it is added to this list. +check-cfg = [ + "cfg(fuzzing)", + "cfg(secp256k1_fuzz)", + "cfg(hashes_fuzz)", +] diff --git a/fuzz/src/bin/chanmon_consistency_target.rs b/fuzz/fuzz-real-hashes/src/bin/chanmon_consistency_target.rs similarity index 94% rename from fuzz/src/bin/chanmon_consistency_target.rs rename to fuzz/fuzz-real-hashes/src/bin/chanmon_consistency_target.rs index 7649900bae5..335c8169c75 100644 --- a/fuzz/src/bin/chanmon_consistency_target.rs +++ b/fuzz/fuzz-real-hashes/src/bin/chanmon_consistency_target.rs @@ -16,8 +16,8 @@ #[cfg(not(fuzzing))] compile_error!("Fuzz targets need cfg=fuzzing"); -#[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +#[cfg(hashes_fuzz)] +compile_error!("Fuzz target does not support cfg(hashes_fuzz)"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - chanmon_consistency_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + chanmon_consistency_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/chanmon_consistency") { + if let Ok(tests) = fs::read_dir("../test_cases/chanmon_consistency") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/bin/gen_target.sh b/fuzz/src/bin/gen_target.sh index fd308a1f10e..868a07652c6 100755 --- a/fuzz/src/bin/gen_target.sh +++ b/fuzz/src/bin/gen_target.sh @@ -2,88 +2,103 @@ echo "#include " > ../../targets.h GEN_TEST() { - cat target_template.txt | sed s/TARGET_NAME/$1/ | sed s/TARGET_MOD/$2$1/ > $1_target.rs - echo "void $1_run(const unsigned char* data, size_t data_len);" >> ../../targets.h + dest_dir=$1 + target_name=$2 + target_mod=$3 + hashes_flag=$4 + + mkdir -p "$dest_dir" + sed "s/TARGET_NAME/$target_name/g; s|TARGET_MOD|$target_mod$target_name|g; s/HASHES_FLAG/$hashes_flag/g" \ + target_template.txt > "$dest_dir/${target_name}_target.rs" + echo "void ${target_name}_run(const unsigned char* data, size_t data_len);" >> ../../targets.h +} + +GEN_FAKE_HASHES_TEST() { + GEN_TEST ../../fuzz-fake-hashes/src/bin "$1" "$2" "not(hashes_fuzz)" +} + +GEN_REAL_HASHES_TEST() { + GEN_TEST ../../fuzz-real-hashes/src/bin "$1" "$2" "hashes_fuzz" } -GEN_TEST bech32_parse -GEN_TEST chanmon_deser -GEN_TEST chanmon_consistency -GEN_TEST full_stack -GEN_TEST invoice_deser -GEN_TEST invoice_request_deser -GEN_TEST offer_deser -GEN_TEST bolt11_deser -GEN_TEST static_invoice_deser -GEN_TEST onion_message -GEN_TEST peer_crypt -GEN_TEST process_network_graph -GEN_TEST process_onion_failure -GEN_TEST refund_deser -GEN_TEST router -GEN_TEST zbase32 -GEN_TEST indexedmap -GEN_TEST onion_hop_data -GEN_TEST base32 -GEN_TEST fromstr_to_netaddress -GEN_TEST feature_flags -GEN_TEST lsps_message -GEN_TEST fs_store -GEN_TEST gossip_discovery +GEN_FAKE_HASHES_TEST bech32_parse +GEN_FAKE_HASHES_TEST chanmon_deser +GEN_REAL_HASHES_TEST chanmon_consistency +GEN_FAKE_HASHES_TEST full_stack +GEN_FAKE_HASHES_TEST invoice_deser +GEN_FAKE_HASHES_TEST invoice_request_deser +GEN_FAKE_HASHES_TEST offer_deser +GEN_FAKE_HASHES_TEST bolt11_deser +GEN_FAKE_HASHES_TEST static_invoice_deser +GEN_FAKE_HASHES_TEST onion_message +GEN_FAKE_HASHES_TEST peer_crypt +GEN_FAKE_HASHES_TEST process_network_graph +GEN_FAKE_HASHES_TEST process_onion_failure +GEN_FAKE_HASHES_TEST refund_deser +GEN_FAKE_HASHES_TEST router +GEN_FAKE_HASHES_TEST zbase32 +GEN_FAKE_HASHES_TEST indexedmap +GEN_FAKE_HASHES_TEST onion_hop_data +GEN_FAKE_HASHES_TEST base32 +GEN_FAKE_HASHES_TEST fromstr_to_netaddress +GEN_FAKE_HASHES_TEST feature_flags +GEN_FAKE_HASHES_TEST lsps_message +GEN_FAKE_HASHES_TEST fs_store +GEN_FAKE_HASHES_TEST gossip_discovery -GEN_TEST msg_accept_channel msg_targets:: -GEN_TEST msg_announcement_signatures msg_targets:: -GEN_TEST msg_channel_reestablish msg_targets:: -GEN_TEST msg_closing_signed msg_targets:: -GEN_TEST msg_closing_complete msg_targets:: -GEN_TEST msg_closing_sig msg_targets:: -GEN_TEST msg_commitment_signed msg_targets:: -GEN_TEST msg_decoded_onion_error_packet msg_targets:: -GEN_TEST msg_funding_created msg_targets:: -GEN_TEST msg_channel_ready msg_targets:: -GEN_TEST msg_funding_signed msg_targets:: -GEN_TEST msg_init msg_targets:: -GEN_TEST msg_open_channel msg_targets:: -GEN_TEST msg_revoke_and_ack msg_targets:: -GEN_TEST msg_shutdown msg_targets:: -GEN_TEST msg_update_fail_htlc msg_targets:: -GEN_TEST msg_update_fail_malformed_htlc msg_targets:: -GEN_TEST msg_update_fee msg_targets:: -GEN_TEST msg_update_fulfill_htlc msg_targets:: +GEN_FAKE_HASHES_TEST msg_accept_channel msg_targets:: +GEN_FAKE_HASHES_TEST msg_announcement_signatures msg_targets:: +GEN_FAKE_HASHES_TEST msg_channel_reestablish msg_targets:: +GEN_FAKE_HASHES_TEST msg_closing_signed msg_targets:: +GEN_FAKE_HASHES_TEST msg_closing_complete msg_targets:: +GEN_FAKE_HASHES_TEST msg_closing_sig msg_targets:: +GEN_FAKE_HASHES_TEST msg_commitment_signed msg_targets:: +GEN_FAKE_HASHES_TEST msg_decoded_onion_error_packet msg_targets:: +GEN_FAKE_HASHES_TEST msg_funding_created msg_targets:: +GEN_FAKE_HASHES_TEST msg_channel_ready msg_targets:: +GEN_FAKE_HASHES_TEST msg_funding_signed msg_targets:: +GEN_FAKE_HASHES_TEST msg_init msg_targets:: +GEN_FAKE_HASHES_TEST msg_open_channel msg_targets:: +GEN_FAKE_HASHES_TEST msg_revoke_and_ack msg_targets:: +GEN_FAKE_HASHES_TEST msg_shutdown msg_targets:: +GEN_FAKE_HASHES_TEST msg_update_fail_htlc msg_targets:: +GEN_FAKE_HASHES_TEST msg_update_fail_malformed_htlc msg_targets:: +GEN_FAKE_HASHES_TEST msg_update_fee msg_targets:: +GEN_FAKE_HASHES_TEST msg_update_fulfill_htlc msg_targets:: -GEN_TEST msg_channel_announcement msg_targets:: -GEN_TEST msg_node_announcement msg_targets:: -GEN_TEST msg_query_short_channel_ids msg_targets:: -GEN_TEST msg_reply_short_channel_ids_end msg_targets:: -GEN_TEST msg_query_channel_range msg_targets:: -GEN_TEST msg_reply_channel_range msg_targets:: -GEN_TEST msg_gossip_timestamp_filter msg_targets:: +GEN_FAKE_HASHES_TEST msg_channel_announcement msg_targets:: +GEN_FAKE_HASHES_TEST msg_node_announcement msg_targets:: +GEN_FAKE_HASHES_TEST msg_query_short_channel_ids msg_targets:: +GEN_FAKE_HASHES_TEST msg_reply_short_channel_ids_end msg_targets:: +GEN_FAKE_HASHES_TEST msg_query_channel_range msg_targets:: +GEN_FAKE_HASHES_TEST msg_reply_channel_range msg_targets:: +GEN_FAKE_HASHES_TEST msg_gossip_timestamp_filter msg_targets:: -GEN_TEST msg_update_add_htlc msg_targets:: -GEN_TEST msg_error_message msg_targets:: -GEN_TEST msg_channel_update msg_targets:: +GEN_FAKE_HASHES_TEST msg_update_add_htlc msg_targets:: +GEN_FAKE_HASHES_TEST msg_error_message msg_targets:: +GEN_FAKE_HASHES_TEST msg_channel_update msg_targets:: -GEN_TEST msg_ping msg_targets:: -GEN_TEST msg_pong msg_targets:: +GEN_FAKE_HASHES_TEST msg_ping msg_targets:: +GEN_FAKE_HASHES_TEST msg_pong msg_targets:: -GEN_TEST msg_channel_details msg_targets:: +GEN_FAKE_HASHES_TEST msg_channel_details msg_targets:: -GEN_TEST msg_open_channel_v2 msg_targets:: -GEN_TEST msg_accept_channel_v2 msg_targets:: -GEN_TEST msg_tx_add_input msg_targets:: -GEN_TEST msg_tx_add_output msg_targets:: -GEN_TEST msg_tx_remove_input msg_targets:: -GEN_TEST msg_tx_remove_output msg_targets:: -GEN_TEST msg_tx_complete msg_targets:: -GEN_TEST msg_tx_signatures msg_targets:: -GEN_TEST msg_tx_init_rbf msg_targets:: -GEN_TEST msg_tx_ack_rbf msg_targets:: -GEN_TEST msg_tx_abort msg_targets:: +GEN_FAKE_HASHES_TEST msg_open_channel_v2 msg_targets:: +GEN_FAKE_HASHES_TEST msg_accept_channel_v2 msg_targets:: +GEN_FAKE_HASHES_TEST msg_tx_add_input msg_targets:: +GEN_FAKE_HASHES_TEST msg_tx_add_output msg_targets:: +GEN_FAKE_HASHES_TEST msg_tx_remove_input msg_targets:: +GEN_FAKE_HASHES_TEST msg_tx_remove_output msg_targets:: +GEN_FAKE_HASHES_TEST msg_tx_complete msg_targets:: +GEN_FAKE_HASHES_TEST msg_tx_signatures msg_targets:: +GEN_FAKE_HASHES_TEST msg_tx_init_rbf msg_targets:: +GEN_FAKE_HASHES_TEST msg_tx_ack_rbf msg_targets:: +GEN_FAKE_HASHES_TEST msg_tx_abort msg_targets:: -GEN_TEST msg_stfu msg_targets:: +GEN_FAKE_HASHES_TEST msg_stfu msg_targets:: -GEN_TEST msg_splice_init msg_targets:: -GEN_TEST msg_splice_ack msg_targets:: -GEN_TEST msg_splice_locked msg_targets:: +GEN_FAKE_HASHES_TEST msg_splice_init msg_targets:: +GEN_FAKE_HASHES_TEST msg_splice_ack msg_targets:: +GEN_FAKE_HASHES_TEST msg_splice_locked msg_targets:: -GEN_TEST msg_blinded_message_path msg_targets:: +GEN_FAKE_HASHES_TEST msg_blinded_message_path msg_targets:: diff --git a/fuzz/src/bin/target_template.txt b/fuzz/src/bin/target_template.txt index 9b0dff8eb8c..78bc7f37d87 100644 --- a/fuzz/src/bin/target_template.txt +++ b/fuzz/src/bin/target_template.txt @@ -16,8 +16,8 @@ #[cfg(not(fuzzing))] compile_error!("Fuzz targets need cfg=fuzzing"); -#[cfg(not(hashes_fuzz))] -compile_error!("Fuzz targets need cfg=hashes_fuzz"); +#[cfg(HASHES_FLAG)] +compile_error!("Fuzz target does not support cfg(HASHES_FLAG)"); #[cfg(not(secp256k1_fuzz))] compile_error!("Fuzz targets need cfg=secp256k1_fuzz"); @@ -71,7 +71,7 @@ fn main() { let mut data = Vec::with_capacity(8192); std::io::stdin().read_to_end(&mut data).unwrap(); - TARGET_NAME_test(&data, lightning_fuzz::utils::test_logger::Stdout {}); + TARGET_NAME_test(&data, test_logger::Stdout {}); } #[test] @@ -87,7 +87,7 @@ fn run_test_cases() { } let mut threads = Vec::new(); let threads_running = Arc::new(atomic::AtomicUsize::new(0)); - if let Ok(tests) = fs::read_dir("test_cases/TARGET_NAME") { + if let Ok(tests) = fs::read_dir("../test_cases/TARGET_NAME") { for test in tests { let mut data: Vec = Vec::new(); let path = test.unwrap().path(); diff --git a/fuzz/src/lib.rs b/fuzz/src/lib.rs index 5f429ea2c3b..25c2fffa23e 100644 --- a/fuzz/src/lib.rs +++ b/fuzz/src/lib.rs @@ -15,9 +15,6 @@ extern crate lightning_rapid_gossip_sync; #[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"); diff --git a/fuzz/test_cases/base32/smoke b/fuzz/test_cases/base32/smoke new file mode 100644 index 00000000000..573541ac970 --- /dev/null +++ b/fuzz/test_cases/base32/smoke @@ -0,0 +1 @@ +0 diff --git a/fuzz/test_cases/bech32_parse/smoke b/fuzz/test_cases/bech32_parse/smoke new file mode 100644 index 00000000000..573541ac970 --- /dev/null +++ b/fuzz/test_cases/bech32_parse/smoke @@ -0,0 +1 @@ +0 diff --git a/fuzz/test_cases/chanmon_consistency/smoke b/fuzz/test_cases/chanmon_consistency/smoke new file mode 100644 index 00000000000..573541ac970 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/smoke @@ -0,0 +1 @@ +0 diff --git a/fuzz/write-seeds/Cargo.toml b/fuzz/write-seeds/Cargo.toml index 6e1952ea8a3..1c5acb7919f 100644 --- a/fuzz/write-seeds/Cargo.toml +++ b/fuzz/write-seeds/Cargo.toml @@ -9,7 +9,3 @@ edition = "2021" [dependencies] lightning-fuzz = { path = "../" } - -# Prevent this from interfering with workspaces -[workspace] -members = ["."] From 37700520a69ffdfe29e304a1f8c8b3e915c6215a Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 14 Apr 2026 12:45:46 +0200 Subject: [PATCH 02/12] Fix chanmon_consistency for real hashes Store real payment preimages in `chanmon_consistency` and use them when claiming funds, so the real-hashes runner does not treat `payment_hash` bytes as a stand-in preimage. AI tools were used in preparing this commit. --- fuzz/README.md | 1 - fuzz/src/chanmon_consistency.rs | 37 ++++++++++++++++++++------------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/fuzz/README.md b/fuzz/README.md index 608b7661304..e4bd3b49ca4 100644 --- a/fuzz/README.md +++ b/fuzz/README.md @@ -56,7 +56,6 @@ cd fuzz export CPU_COUNT=1 # replace as needed export HFUZZ_BUILD_ARGS="--features honggfuzz_fuzz" export HFUZZ_RUN_ARGS="-n $CPU_COUNT --exit_upon_crash" -export HFUZZ_WORKSPACE="./hfuzz_workspace" export TARGET="msg_ping_target" # replace with the target to be fuzzed export RUSTFLAGS="--cfg=fuzzing --cfg=secp256k1_fuzz --cfg=hashes_fuzz" diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index d4a0e560887..73b3f22d4d6 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -568,12 +568,18 @@ type ChanMan<'a> = ChannelManager< >; #[inline] -fn get_payment_secret_hash(dest: &ChanMan, payment_ctr: &mut u64) -> (PaymentSecret, PaymentHash) { +fn get_payment_secret_hash( + dest: &ChanMan, payment_ctr: &mut u64, + payment_preimages: &RefCell>, +) -> (PaymentSecret, PaymentHash) { *payment_ctr += 1; - let payment_hash = PaymentHash(Sha256::hash(&[*payment_ctr as u8]).to_byte_array()); + let mut payment_preimage = PaymentPreimage([0; 32]); + payment_preimage.0[0..8].copy_from_slice(&payment_ctr.to_be_bytes()); + let payment_hash = PaymentHash(Sha256::hash(&payment_preimage.0).to_byte_array()); let payment_secret = dest .create_inbound_payment_for_hash(payment_hash, None, 3600, None) .expect("create_inbound_payment_for_hash failed"); + assert!(payment_preimages.borrow_mut().insert(payment_hash, payment_preimage).is_none()); (payment_secret, payment_hash) } @@ -1344,10 +1350,8 @@ pub fn do_test(data: &[u8], out: Out) { // Create 3 channels between A-B and 3 channels between B-C (6 total). // - // Use version numbers 1-6 to avoid txid collisions under fuzz hashing. - // Fuzz mode uses XOR-based hashing (all bytes XOR to one byte), and - // versions 0-5 cause collisions between A-B and B-C channel pairs - // (e.g., A-B with Version(1) collides with B-C with Version(3)). + // Use distinct version numbers for each funding transaction so each test channel gets its own + // txid and funding outpoint. // A-B: channel 2 A and B have 0-reserve (trusted open + trusted accept), // channel 3 A has 0-reserve (trusted accept) make_channel!(nodes[0], nodes[1], monitor_a, monitor_b, keys_manager_b, 1, false, false); @@ -1424,6 +1428,8 @@ pub fn do_test(data: &[u8], out: Out) { let resolved_payments: RefCell<[HashMap>; 3]> = RefCell::new([new_hash_map(), new_hash_map(), new_hash_map()]); let claimed_payment_hashes: RefCell> = RefCell::new(HashSet::new()); + let payment_preimages: RefCell> = + RefCell::new(new_hash_map()); macro_rules! test_return { () => {{ @@ -1940,9 +1946,8 @@ pub fn do_test(data: &[u8], out: Out) { macro_rules! process_events { ($node: expr, $fail: expr) => {{ - // In case we get 256 payments we may have a hash collision, resulting in the - // second claim/fail call not finding the duplicate-hash HTLC, so we have to - // deduplicate the calls here. + // Multiple HTLCs can resolve for the same payment hash, so deduplicate + // claim/fail handling per event batch. let mut claim_set = new_hash_map(); let mut events = nodes[$node].get_and_clear_pending_events(); let had_events = !events.is_empty(); @@ -1955,7 +1960,11 @@ pub fn do_test(data: &[u8], out: Out) { if $fail { nodes[$node].fail_htlc_backwards(&payment_hash); } else { - nodes[$node].claim_funds(PaymentPreimage(payment_hash.0)); + let payment_preimage = *payment_preimages + .borrow() + .get(&payment_hash) + .expect("PaymentClaimable for unknown payment hash"); + nodes[$node].claim_funds(payment_preimage); claimed_payment_hashes.borrow_mut().insert(payment_hash); } } @@ -2095,7 +2104,7 @@ pub fn do_test(data: &[u8], out: Out) { |source_idx: usize, dest_idx: usize, dest_chan_id, amt, payment_ctr: &mut u64| { let source = &nodes[source_idx]; let dest = &nodes[dest_idx]; - let (secret, hash) = get_payment_secret_hash(dest, payment_ctr); + let (secret, hash) = get_payment_secret_hash(dest, payment_ctr, &payment_preimages); let mut id = PaymentId([0; 32]); id.0[0..8].copy_from_slice(&payment_ctr.to_ne_bytes()); let succeeded = send_payment(source, dest, dest_chan_id, amt, secret, hash, id); @@ -2118,7 +2127,7 @@ pub fn do_test(data: &[u8], out: Out) { let source = &nodes[source_idx]; let middle = &nodes[middle_idx]; let dest = &nodes[dest_idx]; - let (secret, hash) = get_payment_secret_hash(dest, payment_ctr); + let (secret, hash) = get_payment_secret_hash(dest, payment_ctr, &payment_preimages); let mut id = PaymentId([0; 32]); id.0[0..8].copy_from_slice(&payment_ctr.to_ne_bytes()); let succeeded = send_hop_payment( @@ -2145,7 +2154,7 @@ pub fn do_test(data: &[u8], out: Out) { payment_ctr: &mut u64| { let source = &nodes[source_idx]; let dest = &nodes[dest_idx]; - let (secret, hash) = get_payment_secret_hash(dest, payment_ctr); + let (secret, hash) = get_payment_secret_hash(dest, payment_ctr, &payment_preimages); let mut id = PaymentId([0; 32]); id.0[0..8].copy_from_slice(&payment_ctr.to_ne_bytes()); let succeeded = send_mpp_payment(source, dest, dest_chan_ids, amt, secret, hash, id); @@ -2165,7 +2174,7 @@ pub fn do_test(data: &[u8], out: Out) { let source = &nodes[source_idx]; let middle = &nodes[middle_idx]; let dest = &nodes[dest_idx]; - let (secret, hash) = get_payment_secret_hash(dest, payment_ctr); + let (secret, hash) = get_payment_secret_hash(dest, payment_ctr, &payment_preimages); let mut id = PaymentId([0; 32]); id.0[0..8].copy_from_slice(&payment_ctr.to_ne_bytes()); let succeeded = send_mpp_hop_payment( From 25be92a31fd07e2f4209f3e197a0c720a3df8bff Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Thu, 16 Apr 2026 14:09:16 +0200 Subject: [PATCH 03/12] fuzz: relax signing and weight assumptions under fuzzing --- lightning/src/crypto/utils.rs | 16 +++++++++++----- lightning/src/events/bump_transaction/mod.rs | 8 ++++++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/lightning/src/crypto/utils.rs b/lightning/src/crypto/utils.rs index 88911b0baf8..8b2737fa8e9 100644 --- a/lightning/src/crypto/utils.rs +++ b/lightning/src/crypto/utils.rs @@ -67,7 +67,7 @@ pub fn hkdf_extract_expand_7x( #[inline] pub fn sign(ctx: &Secp256k1, msg: &Message, sk: &SecretKey) -> Signature { #[cfg(feature = "grind_signatures")] - let sig = ctx.sign_ecdsa_low_r(msg, sk); + let sig = if cfg!(fuzzing) { ctx.sign_ecdsa(msg, sk) } else { ctx.sign_ecdsa_low_r(msg, sk) }; #[cfg(not(feature = "grind_signatures"))] let sig = ctx.sign_ecdsa(msg, sk); sig @@ -79,10 +79,16 @@ pub fn sign_with_aux_rand( ctx: &Secp256k1, msg: &Message, sk: &SecretKey, entropy_source: &ES, ) -> Signature { #[cfg(feature = "grind_signatures")] - let sig = loop { - let sig = ctx.sign_ecdsa_with_noncedata(msg, sk, &entropy_source.get_secure_random_bytes()); - if sig.serialize_compact()[0] < 0x80 { - break sig; + let sig = { + if cfg!(fuzzing) { + return sign(ctx, msg, sk); + } + loop { + let sig = + ctx.sign_ecdsa_with_noncedata(msg, sk, &entropy_source.get_secure_random_bytes()); + if sig.serialize_compact()[0] < 0x80 { + break sig; + } } }; #[cfg(all(not(feature = "grind_signatures"), not(ldk_test_vectors)))] diff --git a/lightning/src/events/bump_transaction/mod.rs b/lightning/src/events/bump_transaction/mod.rs index 6a5e9948653..ed383ba4f50 100644 --- a/lightning/src/events/bump_transaction/mod.rs +++ b/lightning/src/events/bump_transaction/mod.rs @@ -484,7 +484,11 @@ impl= signed_tx_weight); + // When fuzzing, signatures are trivially small so the actual weight can be + // significantly less than estimated. Skip the lower-bound check. + #[cfg(not(fuzzing))] assert!(expected_signed_tx_weight * 99 / 100 <= signed_tx_weight); let expected_package_fee = Amount::from_sat(fee_for_weight( @@ -732,7 +736,11 @@ impl= signed_tx_weight); + // When fuzzing, signatures are trivially small so the actual weight can be + // significantly less than estimated. Skip the lower-bound check. + #[cfg(not(fuzzing))] assert!(expected_signed_tx_weight * 98 / 100 <= signed_tx_weight); let expected_signed_tx_fee = From 0f73b8b7a741b1d218b197e92135e4fea498127b Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Thu, 16 Apr 2026 14:09:23 +0200 Subject: [PATCH 04/12] ln: handle replayed closed-channel updates on reload --- lightning/src/ln/chanmon_update_fail_tests.rs | 90 ++++++++++++++++++- lightning/src/ln/channelmanager.rs | 28 ++++-- 2 files changed, 110 insertions(+), 8 deletions(-) diff --git a/lightning/src/ln/chanmon_update_fail_tests.rs b/lightning/src/ln/chanmon_update_fail_tests.rs index af4d1569d0c..6960df66b47 100644 --- a/lightning/src/ln/chanmon_update_fail_tests.rs +++ b/lightning/src/ln/chanmon_update_fail_tests.rs @@ -58,7 +58,7 @@ fn test_monitor_and_persister_update_fail() { let chanmon_cfgs = create_chanmon_cfgs(2); let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); - let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); let node_a_id = nodes[0].node.get_our_node_id(); let node_b_id = nodes[1].node.get_our_node_id(); @@ -4609,7 +4609,7 @@ fn test_claim_to_closed_channel_blocks_claimed_event() { let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); let legacy_cfg = test_legacy_channel_config(); let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(legacy_cfg), None]); - let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs); let node_a_id = nodes[0].node.get_our_node_id(); let node_b_id = nodes[1].node.get_our_node_id(); @@ -4653,6 +4653,92 @@ fn test_claim_to_closed_channel_blocks_claimed_event() { expect_payment_claimed!(nodes[1], payment_hash, 1_000_000); } +#[test] +fn test_reload_handles_new_force_close_below_pending_closed_channel_update() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + + let persister; + let chain_mon; + let node_b_reload; + + let legacy_cfg = test_legacy_channel_config(); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(legacy_cfg), None]); + let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let node_a_id = nodes[0].node.get_our_node_id(); + let node_b_id = nodes[1].node.get_our_node_id(); + + let chan_a = + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 500_000_000); + let (payment_preimage, ..) = route_payment(&nodes[0], &[&nodes[1]], 1_000_000); + + // Save monitor bytes from before the close. On reload we'll pair these stale bytes with a + // newer ChannelManager state, forcing startup to regenerate the close update from the monitor's + // current update_id instead of replaying a still-pending close from serialized manager state. + let monitor_before_close = get_monitor!(nodes[1], chan_a.2).encode(); + + let message = "Channel force-closed".to_owned(); + nodes[0] + .node + .force_close_broadcasting_latest_txn(&chan_a.2, &node_b_id, message.clone()) + .unwrap(); + check_added_monitors(&nodes[0], 1); + let a_reason = ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(true), message }; + check_closed_event(&nodes[0], 1, a_reason, &[node_b_id], 1_000_000); + check_closed_broadcast(&nodes[0], 1, true); + + let as_commit_tx = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0); + assert_eq!(as_commit_tx.len(), 1); + + // Let B see A's commitment confirmation normally. This generates and completes the + // CommitmentTxConfirmed force-close monitor update, after which the ChannelManager drops the + // channel from memory and only remembers the latest closed-channel update_id. + mine_transaction(&nodes[1], &as_commit_tx[0]); + check_closed_broadcast(&nodes[1], 1, true); + check_added_monitors(&nodes[1], 1); + let b_reason = ClosureReason::CommitmentTxConfirmed; + check_closed_event(&nodes[1], 1, b_reason, &[node_a_id], 1_000_000); + assert!(nodes[1].node.list_channels().is_empty()); + + // Now create a later closed-channel update and keep it in flight. This is the replayed + // `pending_update` that startup will see first. Because the channel is already closed in the + // ChannelManager, this claim lands on the closed-channel bookkeeping path and gets update_id 4. + chanmon_cfgs[1].persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress); + nodes[1].node.claim_funds(payment_preimage); + check_added_monitors(&nodes[1], 1); + assert!(nodes[1].node.get_and_clear_pending_events().is_empty()); + + // Serialize the newer ChannelManager state, but reload it against the stale pre-close monitor + // bytes captured above. Startup then reconstructs two updates for the same closed channel: + // 1. a regenerated force-close update at id 3, derived from the stale monitor bytes and the + // remembered closed-channel update_id in the ChannelManager, + // 2. the still-in-flight preimage update at id 4, replayed from serialized closed-channel + // in-flight updates. + // + // With the fix in place, startup keeps the regenerated close update ahead of the replayed + // preimage update for this channel, so the stale monitor is brought from 2 -> 3 -> 4. + // The test framework also checks that the chain source watchlist matches the monitor set after + // reload. Because we intentionally go back to stale monitor bytes here, we have to drop the + // old watch registrations before constructing the new ChainMonitor. + nodes[1].chain_source.watched_txn.lock().unwrap().clear(); + nodes[1].chain_source.watched_outputs.lock().unwrap().clear(); + let manager_b = nodes[1].node.encode(); + reload_node!( + nodes[1], + &manager_b, + &[&monitor_before_close], + persister, + chain_mon, + node_b_reload + ); + assert!(nodes[1].node.list_channels().is_empty()); + let events = nodes[1].node.get_and_clear_pending_events(); + assert_eq!(events.len(), 1, "{events:?}"); + assert!(matches!(events[0], Event::PaymentClaimed { .. })); + check_added_monitors(&nodes[1], 2); +} + #[test] #[cfg(all(feature = "std", not(target_os = "windows")))] fn test_single_channel_multiple_mpp() { diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index a3c33b8320f..8ab18d3a0fa 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -19489,8 +19489,8 @@ impl< } } - // The newly generated `close_background_events` have to be added after any updates that - // were already in-flight on shutdown, so we append them here. + // Merge the newly generated close updates with any replayed in-flight updates while keeping + // same-channel updates ordered by `update_id`. pending_background_events.reserve(close_background_events.len()); 'each_bg_event: for mut new_event in close_background_events { if let BackgroundEvent::MonitorUpdateRegeneratedOnStartup { @@ -19506,7 +19506,8 @@ impl< ChannelMonitorUpdateStep::ChannelForceClosed { .. } )); let mut updated_id = false; - for pending_event in pending_background_events.iter() { + let mut insert_idx = pending_background_events.len(); + for (idx, pending_event) in pending_background_events.iter().enumerate() { if let BackgroundEvent::MonitorUpdateRegeneratedOnStartup { counterparty_node_id: pending_cp, funding_txo: pending_funding, @@ -19518,7 +19519,6 @@ impl< && funding_txo == pending_funding && channel_id == pending_chan_id; if for_same_channel { - debug_assert!(update.update_id >= pending_update.update_id); if pending_update.updates.iter().any(|upd| { matches!(upd, ChannelMonitorUpdateStep::ChannelForceClosed { .. }) }) { @@ -19527,8 +19527,19 @@ impl< // force-close update, no need to duplicate it. continue 'each_bg_event; } - update.update_id = pending_update.update_id.saturating_add(1); - updated_id = true; + if update.update_id < pending_update.update_id { + // If we reloaded from stale monitor bytes, the regenerated + // close update may need to run before a later replayed + // post-close update for the same channel. + insert_idx = cmp::min(insert_idx, idx); + } else { + let next_update_id = cmp::max( + update.update_id, + pending_update.update_id.saturating_add(1), + ); + updated_id |= next_update_id != update.update_id; + update.update_id = next_update_id; + } } } } @@ -19551,6 +19562,11 @@ impl< .1; debug_assert!(!in_flight_updates.iter().any(|upd| upd == update)); in_flight_updates.push(update.clone()); + in_flight_updates.sort_by_key(|upd| upd.update_id); + if insert_idx < pending_background_events.len() { + pending_background_events.insert(insert_idx, new_event); + continue; + } } pending_background_events.push(new_event); } From a5376847a346522b4f694d541fa0c930ee0fb028 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Thu, 16 Apr 2026 14:20:55 +0200 Subject: [PATCH 05/12] fuzz: make chanmon_consistency chain sync and progress realistic --- fuzz/src/chanmon_consistency.rs | 795 +++++++++++++++++++++++++++----- 1 file changed, 691 insertions(+), 104 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 73b3f22d4d6..a4d21a80502 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -27,6 +27,7 @@ use bitcoin::script::{Builder, ScriptBuf}; use bitcoin::transaction::Version; use bitcoin::transaction::{Transaction, TxOut}; use bitcoin::FeeRate; +use bitcoin::OutPoint as BitcoinOutPoint; use bitcoin::block::Header; use bitcoin::hash_types::Txid; @@ -41,12 +42,12 @@ use lightning::chain; use lightning::chain::chaininterface::{ BroadcasterInterface, ConfirmationTarget, FeeEstimator, TransactionType, }; -use lightning::chain::channelmonitor::{ChannelMonitor, MonitorEvent}; +use lightning::chain::channelmonitor::{Balance, ChannelMonitor, MonitorEvent}; use lightning::chain::transaction::OutPoint; use lightning::chain::{ chainmonitor, channelmonitor, BestBlock, ChannelMonitorUpdateStatus, Confirm, Watch, }; -use lightning::events; +use lightning::events::{self, EventsProvider}; use lightning::ln::channel::{ FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE, MAX_STD_OUTPUT_DUST_LIMIT_SATOSHIS, }; @@ -85,6 +86,8 @@ use lightning::util::test_channel_signer::{EnforcementState, SignerOp, TestChann use lightning::util::test_utils::TestWalletSource; use lightning::util::wallet_utils::{WalletSourceSync, WalletSync}; +use lightning::events::bump_transaction::sync::BumpTransactionEventHandlerSync; + use lightning_invoice::RawBolt11Invoice; use crate::utils::test_logger::{self, Output}; @@ -186,13 +189,22 @@ impl BroadcasterInterface for TestBroadcaster { struct ChainState { blocks: Vec<(Header, Vec)>, confirmed_txids: HashSet, + /// Tracks unspent outputs created by confirmed transactions. Only + /// transactions that spend existing UTXOs can be confirmed, which + /// prevents fuzz hash collisions from creating phantom spends of + /// outputs that were never actually created. + utxos: HashSet, } impl ChainState { fn new() -> Self { let genesis_hash = genesis_block(Network::Bitcoin).block_hash(); let genesis_header = create_dummy_header(genesis_hash, 42); - Self { blocks: vec![(genesis_header, Vec::new())], confirmed_txids: HashSet::new() } + Self { + blocks: vec![(genesis_header, Vec::new())], + confirmed_txids: HashSet::new(), + utxos: HashSet::new(), + } } fn tip_height(&self) -> u32 { @@ -204,7 +216,39 @@ impl ChainState { if self.confirmed_txids.contains(&txid) { return false; } + // Reject timelocked transactions before their lock_time, matching + // consensus rules. Commitment txs encode an obscured commitment + // number with bit 29 set, which is not a real timelock. + let lock_time = tx.lock_time.to_consensus_u32(); + if lock_time > 0 + && lock_time < 500_000_000 + && lock_time & (1 << 29) == 0 + && self.tip_height() < lock_time + { + return false; + } + // Validate that all inputs spend existing, unspent outputs. This + // rejects both double-spends and spends of outputs that were never + // created (e.g. due to fuzz txid hash collisions where a different + // transaction was confirmed under the same txid). + let is_coinbase = tx.is_coinbase(); + if !is_coinbase { + for input in &tx.input { + if !self.utxos.contains(&input.previous_output) { + return false; + } + } + } self.confirmed_txids.insert(txid); + if !is_coinbase { + for input in &tx.input { + self.utxos.remove(&input.previous_output); + } + } + // Add this transaction's outputs as new UTXOs. + for idx in 0..tx.output.len() { + self.utxos.insert(BitcoinOutPoint { txid, vout: idx as u32 }); + } let prev_hash = self.blocks.last().unwrap().0.block_hash(); let header = create_dummy_header(prev_hash, 42); @@ -218,6 +262,14 @@ impl ChainState { true } + fn advance_height(&mut self, num_blocks: u32) { + for _ in 0..num_blocks { + let prev_hash = self.blocks.last().unwrap().0.block_hash(); + let header = create_dummy_header(prev_hash, 42); + self.blocks.push((header, Vec::new())); + } + } + fn block_at(&self, height: u32) -> &(Header, Vec) { &self.blocks[height as usize] } @@ -711,24 +763,25 @@ fn send_mpp_payment( source: &ChanMan, dest: &ChanMan, dest_chan_ids: &[ChannelId], amt: u64, payment_secret: PaymentSecret, payment_hash: PaymentHash, payment_id: PaymentId, ) -> bool { - let num_paths = dest_chan_ids.len(); + let mut paths = Vec::new(); + + let dest_chans = dest.list_channels(); + let dest_scids: Vec<_> = dest_chan_ids + .iter() + .filter_map(|chan_id| { + dest_chans + .iter() + .find(|chan| chan.channel_id == *chan_id) + .and_then(|chan| chan.short_channel_id) + }) + .collect(); + let num_paths = dest_scids.len(); if num_paths == 0 { return false; } - let amt_per_path = amt / num_paths as u64; - let mut paths = Vec::with_capacity(num_paths); - let dest_chans = dest.list_channels(); - let dest_scids = dest_chan_ids.iter().map(|chan_id| { - dest_chans - .iter() - .find(|chan| chan.channel_id == *chan_id) - .and_then(|chan| chan.short_channel_id) - .unwrap() - }); - - for (i, dest_scid) in dest_scids.enumerate() { + for (i, dest_scid) in dest_scids.into_iter().enumerate() { let path_amt = if i == num_paths - 1 { amt - amt_per_path * (num_paths as u64 - 1) } else { @@ -770,41 +823,38 @@ fn send_mpp_hop_payment( dest_chan_ids: &[ChannelId], amt: u64, payment_secret: PaymentSecret, payment_hash: PaymentHash, payment_id: PaymentId, ) -> bool { - // Create paths by pairing middle_scids with dest_scids - let num_paths = middle_chan_ids.len().max(dest_chan_ids.len()); - if num_paths == 0 { - return false; - } - - let first_hop_fee = 50_000; - let amt_per_path = amt / num_paths as u64; - let fee_per_path = first_hop_fee / num_paths as u64; - let mut paths = Vec::with_capacity(num_paths); - let middle_chans = middle.list_channels(); let middle_scids: Vec<_> = middle_chan_ids .iter() - .map(|chan_id| { + .filter_map(|chan_id| { middle_chans .iter() .find(|chan| chan.channel_id == *chan_id) .and_then(|chan| chan.short_channel_id) - .unwrap() }) .collect(); let dest_chans = dest.list_channels(); let dest_scids: Vec<_> = dest_chan_ids .iter() - .map(|chan_id| { + .filter_map(|chan_id| { dest_chans .iter() .find(|chan| chan.channel_id == *chan_id) .and_then(|chan| chan.short_channel_id) - .unwrap() }) .collect(); + let num_paths = middle_scids.len().max(dest_scids.len()); + if middle_scids.is_empty() || dest_scids.is_empty() { + return false; + } + + let first_hop_fee = 50_000; + let amt_per_path = amt / num_paths as u64; + let fee_per_path = first_hop_fee / num_paths as u64; + let mut paths = Vec::with_capacity(num_paths); + for i in 0..num_paths { let middle_scid = middle_scids[i % middle_scids.len()]; let dest_scid = dest_scids[i % dest_scids.len()]; @@ -858,17 +908,6 @@ fn send_mpp_hop_payment( } } -#[inline] -fn assert_action_timeout_awaiting_response(action: &msgs::ErrorAction) { - // Since sending/receiving messages may be delayed, `timer_tick_occurred` may cause a node to - // disconnect their counterparty if they're expecting a timely response. - assert!(matches!( - action, - msgs::ErrorAction::DisconnectPeerWithWarning { msg } - if msg.data.contains("Disconnecting due to timeout awaiting response") - )); -} - enum ChanType { Legacy, KeyedAnchors, @@ -913,6 +952,43 @@ pub fn do_test(data: &[u8], out: Out) { let mut node_height_a: u32 = 0; let mut node_height_b: u32 = 0; let mut node_height_c: u32 = 0; + let has_timed_balance = |balances: &[Balance]| { + balances.iter().any(|balance| { + matches!( + balance, + // Force-close settlement only needs to stretch height when + // there is still time-sensitive work that may release new + // on-chain claims. `ClaimableAwaitingConfirmations` is not in + // that bucket, it means some transaction already exists and we + // are mostly waiting for more confirms on it. + Balance::ContentiousClaimable { .. } + | Balance::MaybeTimeoutClaimableHTLC { .. } + | Balance::MaybePreimageClaimableHTLC { .. } + | Balance::CounterpartyRevokedOutputClaimable { .. } + ) + }) + }; + let summarize_balances = |balances: &[Balance]| -> String { + let mut on_close = 0; + let mut awaiting = 0; + let mut contentious = 0; + let mut maybe_timeout = 0; + let mut maybe_preimage = 0; + let mut revoked = 0; + for balance in balances { + match balance { + Balance::ClaimableOnChannelClose { .. } => on_close += 1, + Balance::ClaimableAwaitingConfirmations { .. } => awaiting += 1, + Balance::ContentiousClaimable { .. } => contentious += 1, + Balance::MaybeTimeoutClaimableHTLC { .. } => maybe_timeout += 1, + Balance::MaybePreimageClaimableHTLC { .. } => maybe_preimage += 1, + Balance::CounterpartyRevokedOutputClaimable { .. } => revoked += 1, + } + } + format!( + "on_close={on_close} awaiting={awaiting} contentious={contentious} maybe_timeout={maybe_timeout} maybe_preimage={maybe_preimage} revoked={revoked}" + ) + }; macro_rules! make_node { ($node_id: expr, $fee_estimator: expr, $broadcaster: expr) => {{ @@ -1076,13 +1152,24 @@ pub fn do_test(data: &[u8], out: Out) { let manager = <(BestBlock, ChanMan)>::read(&mut &ser[..], read_args).expect("Failed to read manager"); let res = (manager.1, chain_monitor.clone()); + let expected_status = *mon_style[node_id as usize].borrow(); + *chain_monitor.persister.update_ret.lock().unwrap() = expected_status.clone(); for (channel_id, mon) in monitors.drain() { + let monitor_id = mon.get_latest_update_id(); assert_eq!( chain_monitor.chain_monitor.watch_channel(channel_id, mon), - Ok(ChannelMonitorUpdateStatus::Completed) + Ok(expected_status.clone()) ); + // We call the inner `ChainMonitor::watch_channel` directly during + // restart, so mirror its initial in-progress update in + // `latest_monitors` for the harness bookkeeping. + if expected_status == chain::ChannelMonitorUpdateStatus::InProgress { + let mut map = chain_monitor.latest_monitors.lock().unwrap(); + if let Some(state) = map.get_mut(&channel_id) { + state.pending_monitors.push((monitor_id, state.persisted_monitor.clone())); + } + } } - *chain_monitor.persister.update_ret.lock().unwrap() = *mon_style[node_id as usize].borrow(); res }; @@ -1295,9 +1382,18 @@ pub fn do_test(data: &[u8], out: Out) { for node in $nodes.iter() { let events = node.get_and_clear_pending_msg_events(); for event in events { - if let MessageSendEvent::SendAnnouncementSignatures { .. } = event { - } else { - panic!("Wrong event type"); + match event { + MessageSendEvent::SendAnnouncementSignatures { .. } => {}, + MessageSendEvent::SendChannelUpdate { ref node_id, ref msg } => { + for dest_node in $nodes.iter() { + if dest_node.get_our_node_id() == *node_id { + dest_node.handle_channel_update(node.get_our_node_id(), msg); + } + } + }, + _ => { + panic!("Wrong event type in second lock_fundings pass: {:?}", event); + }, } } } @@ -1309,21 +1405,55 @@ pub fn do_test(data: &[u8], out: Out) { let wallet_c = TestWalletSource::new(SecretKey::from_slice(&[3; 32]).unwrap()); let wallets = vec![wallet_a, wallet_b, wallet_c]; - let coinbase_tx = bitcoin::Transaction { - version: bitcoin::transaction::Version::TWO, - lock_time: bitcoin::absolute::LockTime::ZERO, - input: vec![bitcoin::TxIn { ..Default::default() }], - output: wallets - .iter() - .map(|w| TxOut { - value: Amount::from_sat(100_000), - script_pubkey: w.get_change_script().unwrap(), - }) - .collect(), + // Create wallet UTXOs for each node. Each anchor-channel HTLC claim + // needs a wallet input for fees, so we create enough UTXOs to cover + // multiple concurrent claims. + let num_wallet_utxos = 50; + for (wallet_idx, w) in wallets.iter().enumerate() { + let coinbase_tx = bitcoin::Transaction { + version: bitcoin::transaction::Version(wallet_idx as i32 + 100), + lock_time: bitcoin::absolute::LockTime::ZERO, + input: vec![bitcoin::TxIn { ..Default::default() }], + output: (0..num_wallet_utxos) + .map(|_| TxOut { + value: Amount::from_sat(100_000), + script_pubkey: w.get_change_script().unwrap(), + }) + .collect(), + }; + for vout in 0..num_wallet_utxos { + w.add_utxo(coinbase_tx.clone(), vout); + } + chain_state.confirm_tx(coinbase_tx); + } + let sync_wallets_with_confirmed_tx = |tx: &Transaction| { + for wallet in &wallets { + let change_script = wallet.get_change_script().unwrap(); + for input in &tx.input { + wallet.remove_utxo(input.previous_output); + } + for (vout, output) in tx.output.iter().enumerate() { + if output.script_pubkey == change_script { + wallet.add_utxo(tx.clone(), vout as u32); + } + } + } + }; + let confirm_tx_and_sync_wallets = |chain_state: &mut ChainState, tx: Transaction| -> bool { + if chain_state.confirm_tx(tx.clone()) { + sync_wallets_with_confirmed_tx(&tx); + true + } else { + false + } + }; + let should_retry_confirm_later = |chain_state: &ChainState, tx: &Transaction| { + let lock_time = tx.lock_time.to_consensus_u32(); + lock_time > 0 + && lock_time < 500_000_000 + && lock_time & (1 << 29) == 0 + && chain_state.tip_height() < lock_time }; - wallets.iter().enumerate().for_each(|(i, w)| { - w.add_utxo(coinbase_tx.clone(), i as u32); - }); let fee_est_a = Arc::new(FuzzEstimator { ret_val: atomic::AtomicU32::new(253) }); let mut last_htlc_clear_fee_a = 253; @@ -1350,8 +1480,11 @@ pub fn do_test(data: &[u8], out: Out) { // Create 3 channels between A-B and 3 channels between B-C (6 total). // - // Use distinct version numbers for each funding transaction so each test channel gets its own - // txid and funding outpoint. + // Use distinct version numbers 1-6 so each test channel gets its own txid and funding + // outpoint, and to avoid txid collisions under fuzz hashing. + // Fuzz mode uses XOR-based hashing (all bytes XOR to one byte), and versions 0-5 cause + // collisions between A-B and B-C channel pairs (e.g., A-B with Version(1) collides with + // B-C with Version(3)). // A-B: channel 2 A and B have 0-reserve (trusted open + trusted accept), // channel 3 A has 0-reserve (trusted accept) make_channel!(nodes[0], nodes[1], monitor_a, monitor_b, keys_manager_b, 1, false, false); @@ -1371,6 +1504,7 @@ pub fn do_test(data: &[u8], out: Out) { let sync_with_chain_state = |chain_state: &ChainState, node: &ChannelManager<_, _, _, _, _, _, _, _, _>, + monitor: &TestChainMonitor, node_height: &mut u32, num_blocks: Option| { let target_height = if let Some(num_blocks) = num_blocks { @@ -1380,20 +1514,39 @@ pub fn do_test(data: &[u8], out: Out) { }; while *node_height < target_height { - *node_height += 1; + let mut next_height = *node_height + 1; + while next_height <= target_height && chain_state.block_at(next_height).1.is_empty() { + next_height += 1; + } + if next_height > target_height { + *node_height = target_height; + let (header, _) = chain_state.block_at(*node_height); + monitor.chain_monitor.best_block_updated(header, *node_height); + node.best_block_updated(header, *node_height); + break; + } + if next_height > *node_height + 1 { + *node_height = next_height - 1; + let (header, _) = chain_state.block_at(*node_height); + monitor.chain_monitor.best_block_updated(header, *node_height); + node.best_block_updated(header, *node_height); + } + *node_height = next_height; let (header, txn) = chain_state.block_at(*node_height); let txdata: Vec<_> = txn.iter().enumerate().map(|(i, tx)| (i + 1, tx)).collect(); if !txdata.is_empty() { + monitor.chain_monitor.transactions_confirmed(header, &txdata, *node_height); node.transactions_confirmed(header, &txdata, *node_height); } + monitor.chain_monitor.best_block_updated(header, *node_height); node.best_block_updated(header, *node_height); } }; // Sync all nodes to tip to lock the funding. - sync_with_chain_state(&mut chain_state, &nodes[0], &mut node_height_a, None); - sync_with_chain_state(&mut chain_state, &nodes[1], &mut node_height_b, None); - sync_with_chain_state(&mut chain_state, &nodes[2], &mut node_height_c, None); + sync_with_chain_state(&mut chain_state, &nodes[0], &monitor_a, &mut node_height_a, None); + sync_with_chain_state(&mut chain_state, &nodes[1], &monitor_b, &mut node_height_b, None); + sync_with_chain_state(&mut chain_state, &nodes[2], &monitor_c, &mut node_height_c, None); lock_fundings!(nodes); @@ -1433,9 +1586,9 @@ pub fn do_test(data: &[u8], out: Out) { macro_rules! test_return { () => {{ - assert_eq!(nodes[0].list_channels().len(), 3); - assert_eq!(nodes[1].list_channels().len(), 6); - assert_eq!(nodes[2].list_channels().len(), 3); + assert!(nodes[0].list_channels().len() <= 3); + assert!(nodes[1].list_channels().len() <= 6); + assert!(nodes[2].list_channels().len() <= 3); // All broadcasters should be empty (all broadcast transactions should be handled // explicitly). @@ -1622,7 +1775,13 @@ pub fn do_test(data: &[u8], out: Out) { *node_id == a_id }, MessageSendEvent::HandleError { ref action, ref node_id } => { - assert_action_timeout_awaiting_response(action); + match action { + msgs::ErrorAction::DisconnectPeerWithWarning { msg } + if msg.data.contains("Disconnecting due to timeout awaiting response") => {}, + msgs::ErrorAction::DisconnectPeer { .. } => {}, + msgs::ErrorAction::SendErrorMessage { .. } => {}, + _ => panic!("Unexpected HandleError action {:?}", action), + } if Some(*node_id) == expect_drop_id { panic!("peer_disconnected should drop msgs bound for the disconnected peer"); } *node_id == a_id }, @@ -1740,6 +1899,17 @@ pub fn do_test(data: &[u8], out: Out) { } }, MessageSendEvent::SendChannelReestablish { ref node_id, ref msg } => { + if msg.next_local_commitment_number == 0 + && msg.next_remote_commitment_number == 0 + { + // Skip bogus reestablish (lnd workaround). All fuzzer + // nodes are LDK and will already force-close via the + // error message path. Delivering these between LDK + // nodes creates an infinite ping-pong since both sides + // respond with another bogus reestablish for the + // unknown channel. + continue; + } for (idx, dest) in nodes.iter().enumerate() { if dest.get_our_node_id() == *node_id { out.locked_write(format!("Delivering channel_reestablish from node {} to node {}.\n", $node, idx).as_bytes()); @@ -1851,22 +2021,51 @@ pub fn do_test(data: &[u8], out: Out) { } } }, - MessageSendEvent::HandleError { ref action, .. } => { - assert_action_timeout_awaiting_response(action); - }, - MessageSendEvent::SendChannelReady { .. } => { - // Can be generated as a reestablish response - }, - MessageSendEvent::SendAnnouncementSignatures { .. } => { - // Can be generated as a reestablish response - }, - MessageSendEvent::SendChannelUpdate { .. } => { - // Can be generated as a reestablish response + MessageSendEvent::HandleError { ref action, ref node_id } => { + match action { + msgs::ErrorAction::DisconnectPeerWithWarning { msg } + if msg.data.contains("Disconnecting due to timeout awaiting response") => {}, + msgs::ErrorAction::DisconnectPeer { .. } => {}, + msgs::ErrorAction::SendErrorMessage { ref msg } => { + for dest in nodes.iter() { + if dest.get_our_node_id() == *node_id { + dest.handle_error(nodes[$node].get_our_node_id(), msg); + } + } + }, + _ => panic!("Unexpected HandleError action {:?}", action), + } }, + MessageSendEvent::SendChannelReady { ref node_id, ref msg } => { + for (idx, dest) in nodes.iter().enumerate() { + if dest.get_our_node_id() == *node_id { + out.locked_write(format!("Delivering channel_ready from node {} to node {}.\n", $node, idx).as_bytes()); + dest.handle_channel_ready(nodes[$node].get_our_node_id(), msg); + } + } + }, + MessageSendEvent::SendAnnouncementSignatures { ref node_id, ref msg } => { + for (idx, dest) in nodes.iter().enumerate() { + if dest.get_our_node_id() == *node_id { + out.locked_write(format!("Delivering announcement_signatures from node {} to node {}.\n", $node, idx).as_bytes()); + dest.handle_announcement_signatures(nodes[$node].get_our_node_id(), msg); + } + } + }, + MessageSendEvent::SendChannelUpdate { ref node_id, ref msg } => { + for (idx, dest) in nodes.iter().enumerate() { + if dest.get_our_node_id() == *node_id { + out.locked_write(format!("Delivering channel_update from node {} to node {}.\n", $node, idx).as_bytes()); + dest.handle_channel_update(nodes[$node].get_our_node_id(), msg); + } + } + }, MessageSendEvent::BroadcastChannelUpdate { .. } => { // Can be generated as a result of calling `timer_tick_occurred` enough // times while peers are disconnected }, + MessageSendEvent::BroadcastChannelAnnouncement { .. } => {}, + MessageSendEvent::BroadcastNodeAnnouncement { .. } => {}, _ => panic!("Unhandled message event {:?}", event), } if $limit_events != ProcessMessages::AllMessages { @@ -1904,9 +2103,17 @@ pub fn do_test(data: &[u8], out: Out) { MessageSendEvent::SendChannelReady { .. } => {}, MessageSendEvent::SendAnnouncementSignatures { .. } => {}, MessageSendEvent::BroadcastChannelUpdate { .. } => {}, + MessageSendEvent::BroadcastChannelAnnouncement { .. } => {}, + MessageSendEvent::BroadcastNodeAnnouncement { .. } => {}, MessageSendEvent::SendChannelUpdate { .. } => {}, - MessageSendEvent::HandleError { ref action, .. } => { - assert_action_timeout_awaiting_response(action); + MessageSendEvent::HandleError { ref action, .. } => match action { + msgs::ErrorAction::DisconnectPeerWithWarning { msg } + if msg.data.contains( + "Disconnecting due to timeout awaiting response", + ) => {}, + msgs::ErrorAction::DisconnectPeer { .. } => {}, + msgs::ErrorAction::SendErrorMessage { .. } => {}, + _ => panic!("Unexpected HandleError action {:?}", action), }, _ => panic!("Unhandled message event"), } @@ -1927,9 +2134,17 @@ pub fn do_test(data: &[u8], out: Out) { MessageSendEvent::SendChannelReady { .. } => {}, MessageSendEvent::SendAnnouncementSignatures { .. } => {}, MessageSendEvent::BroadcastChannelUpdate { .. } => {}, + MessageSendEvent::BroadcastChannelAnnouncement { .. } => {}, + MessageSendEvent::BroadcastNodeAnnouncement { .. } => {}, MessageSendEvent::SendChannelUpdate { .. } => {}, - MessageSendEvent::HandleError { ref action, .. } => { - assert_action_timeout_awaiting_response(action); + MessageSendEvent::HandleError { ref action, .. } => match action { + msgs::ErrorAction::DisconnectPeerWithWarning { msg } + if msg.data.contains( + "Disconnecting due to timeout awaiting response", + ) => {}, + msgs::ErrorAction::DisconnectPeer { .. } => {}, + msgs::ErrorAction::SendErrorMessage { .. } => {}, + _ => panic!("Unexpected HandleError action {:?}", action), }, _ => panic!("Unhandled message event"), } @@ -2012,6 +2227,16 @@ pub fn do_test(data: &[u8], out: Out) { events::Event::PaymentPathFailed { .. } => {}, events::Event::PaymentForwarded { .. } if $node == 1 => {}, events::Event::ChannelReady { .. } => {}, + events::Event::HTLCHandlingFailed { + failure_type: events::HTLCHandlingFailureType::Receive { payment_hash }, + .. + } => { + // The receiver failed to handle this HTLC (e.g., HTLC + // timeout won the race against the claim). Remove it from + // claimed hashes so we don't assert that the sender must + // have received PaymentSent. + claimed_payment_hashes.borrow_mut().remove(&payment_hash); + }, events::Event::HTLCHandlingFailed { .. } => {}, events::Event::FundingTransactionReadyForSigning { @@ -2030,16 +2255,24 @@ pub fn do_test(data: &[u8], out: Out) { .unwrap(); }, events::Event::SplicePending { new_funding_txo, .. } => { - let broadcaster = match $node { - 0 => &broadcast_a, - 1 => &broadcast_b, - _ => &broadcast_c, - }; - let mut txs = broadcaster.txn_broadcasted.borrow_mut(); - assert!(txs.len() >= 1); - let splice_tx = txs.remove(0); - assert_eq!(new_funding_txo.txid, splice_tx.compute_txid()); - chain_state.confirm_tx(splice_tx); + if !chain_state.confirmed_txids.contains(&new_funding_txo.txid) { + let broadcaster = match $node { + 0 => &broadcast_a, + 1 => &broadcast_b, + _ => &broadcast_c, + }; + let mut txs = broadcaster.txn_broadcasted.borrow_mut(); + if let Some(pos) = txs + .iter() + .position(|tx| new_funding_txo.txid == tx.compute_txid()) + { + let splice_tx = txs.remove(pos); + confirm_tx_and_sync_wallets(&mut chain_state, splice_tx); + } + // If not found, the settlement drain loop already + // removed it from the broadcaster but confirm_tx + // rejected it (e.g. inputs already spent). + } }, events::Event::SpliceFailed { .. } => {}, events::Event::DiscardFunding { @@ -2076,7 +2309,7 @@ pub fn do_test(data: &[u8], out: Out) { ); if let Some((id, data)) = compl_selector(&mut state.pending_monitors) { monitor.chain_monitor.channel_monitor_updated(*chan_funding, id).unwrap(); - if id > state.persisted_monitor_id { + if id >= state.persisted_monitor_id { state.persisted_monitor_id = id; state.persisted_monitor = data; } @@ -2085,19 +2318,22 @@ pub fn do_test(data: &[u8], out: Out) { }; let complete_all_monitor_updates = |monitor: &Arc, chan_id| { + let mut completed_any = false; if let Some(state) = monitor.latest_monitors.lock().unwrap().get_mut(chan_id) { assert!( state.pending_monitors.windows(2).all(|pair| pair[0].0 < pair[1].0), "updates should be sorted by id" ); for (id, data) in state.pending_monitors.drain(..) { + completed_any = true; monitor.chain_monitor.channel_monitor_updated(*chan_id, id).unwrap(); - if id > state.persisted_monitor_id { + if id >= state.persisted_monitor_id { state.persisted_monitor_id = id; state.persisted_monitor = data; } } } + completed_any }; let send = @@ -2193,6 +2429,321 @@ pub fn do_test(data: &[u8], out: Out) { } }; + macro_rules! has_pending_monitor_updates { + () => {{ + [&monitor_a, &monitor_b, &monitor_c].iter().any(|monitor| { + monitor + .latest_monitors + .lock() + .unwrap() + .values() + .any(|state| !state.pending_monitors.is_empty()) + }) + }}; + } + macro_rules! has_time_dependent_work { + () => {{ + let open_channels = nodes[0] + .list_channels() + .iter() + .chain(nodes[1].list_channels().iter()) + .chain(nodes[2].list_channels().iter()) + .cloned() + .collect::>(); + let open_refs: Vec<&_> = open_channels.iter().collect(); + [&monitor_a, &monitor_b, &monitor_c].iter().any(|monitor| { + monitor.chain_monitor.get_claimable_balances(&open_refs).iter().any(|balance| { + matches!( + balance, + Balance::ContentiousClaimable { .. } + | Balance::MaybeTimeoutClaimableHTLC { .. } + | Balance::MaybePreimageClaimableHTLC { .. } + | Balance::CounterpartyRevokedOutputClaimable { .. } + ) + }) + }) + }}; + } + macro_rules! has_pending_work { + () => {{ + !ab_events.is_empty() + || !ba_events.is_empty() + || !bc_events.is_empty() + || !cb_events.is_empty() + || !broadcast_a.txn_broadcasted.borrow().is_empty() + || !broadcast_b.txn_broadcasted.borrow().is_empty() + || !broadcast_c.txn_broadcasted.borrow().is_empty() + || has_pending_monitor_updates!() + || has_time_dependent_work!() + }}; + } + macro_rules! pending_work_summary { + () => {{ + let open_channels = nodes[0] + .list_channels() + .iter() + .chain(nodes[1].list_channels().iter()) + .chain(nodes[2].list_channels().iter()) + .cloned() + .collect::>(); + let open_refs: Vec<&_> = open_channels.iter().collect(); + let balances_a = monitor_a.chain_monitor.get_claimable_balances(&open_refs); + let balances_b = monitor_b.chain_monitor.get_claimable_balances(&open_refs); + let balances_c = monitor_c.chain_monitor.get_claimable_balances(&open_refs); + format!( + "queues ab={} ba={} bc={} cb={} bcast=({},{},{}) pending=({},{},{}) monitor_updates={} timed_work={} heights=({},{},{}) tip={} balances_a=[{}] balances_b=[{}] balances_c=[{}]", + ab_events.len(), + ba_events.len(), + bc_events.len(), + cb_events.len(), + broadcast_a.txn_broadcasted.borrow().len(), + broadcast_b.txn_broadcasted.borrow().len(), + broadcast_c.txn_broadcasted.borrow().len(), + pending_payments.borrow()[0].len(), + pending_payments.borrow()[1].len(), + pending_payments.borrow()[2].len(), + has_pending_monitor_updates!(), + has_time_dependent_work!(), + node_height_a, + node_height_b, + node_height_c, + chain_state.tip_height(), + summarize_balances(&balances_a), + summarize_balances(&balances_b), + summarize_balances(&balances_c), + ) + }}; + } + + macro_rules! flush_progress { + ($max_iters: expr) => {{ + let mut last_pass_no_updates = false; + for _ in 0..$max_iters { + let mut completed_monitor_update = false; + for id in &chan_ab_ids { + completed_monitor_update |= complete_all_monitor_updates(&monitor_a, id); + completed_monitor_update |= complete_all_monitor_updates(&monitor_b, id); + } + for id in &chan_bc_ids { + completed_monitor_update |= complete_all_monitor_updates(&monitor_b, id); + completed_monitor_update |= complete_all_monitor_updates(&monitor_c, id); + } + let mut had_msg_or_ev = false; + if process_msg_events!(0, false, ProcessMessages::AllMessages) { + had_msg_or_ev = true; + } + if process_msg_events!(1, false, ProcessMessages::AllMessages) { + had_msg_or_ev = true; + } + if process_msg_events!(2, false, ProcessMessages::AllMessages) { + had_msg_or_ev = true; + } + if process_events!(0, false) { + had_msg_or_ev = true; + } + if process_events!(1, false) { + had_msg_or_ev = true; + } + if process_events!(2, false) { + had_msg_or_ev = true; + } + sync_with_chain_state( + &chain_state, + &nodes[0], + &monitor_a, + &mut node_height_a, + None, + ); + sync_with_chain_state( + &chain_state, + &nodes[1], + &monitor_b, + &mut node_height_b, + None, + ); + sync_with_chain_state( + &chain_state, + &nodes[2], + &monitor_c, + &mut node_height_c, + None, + ); + { + let monitors = [&monitor_a, &monitor_b, &monitor_c]; + let broadcasters: [&Arc; 3] = + [&broadcast_a, &broadcast_b, &broadcast_c]; + let keys_managers = [&keys_manager_a, &keys_manager_b, &keys_manager_c]; + for (idx, monitor) in monitors.iter().enumerate() { + let wallet = WalletSync::new(&wallets[idx], Arc::clone(&loggers[idx])); + let handler = BumpTransactionEventHandlerSync::new( + broadcasters[idx].as_ref(), + &wallet, + keys_managers[idx].as_ref(), + Arc::clone(&loggers[idx]), + ); + let broadcaster = broadcasters[idx]; + monitor.chain_monitor.process_pending_events( + &|event: events::Event| { + if let events::Event::BumpTransaction(ref bump) = event { + match bump { + // Commitment transactions are already + // fully assembled by LDK when a channel + // closes, so the harness can broadcast + // them directly. + events::bump_transaction::BumpTransactionEvent::ChannelClose { + commitment_tx, + channel_id, + counterparty_node_id, + .. + } => { + broadcaster.broadcast_transactions(&[( + commitment_tx, + lightning::chain::chaininterface::TransactionType::UnilateralClose { + counterparty_node_id: *counterparty_node_id, + channel_id: *channel_id, + }, + )]); + }, + // HTLC resolution events need wallet-backed + // coin selection and signer access. Route + // those through the sync bump handler so the + // resulting claims look like real post-close + // transactions. + events::bump_transaction::BumpTransactionEvent::HTLCResolution { .. } => { + handler.handle_event(bump); + }, + } + } + Ok(()) + }, + ); + } + } + let mut had_new_txs = false; + for confirm_iter in 0..32 { + let mut found = false; + let mut pending_txs: Vec = Vec::new(); + for tx in broadcast_a.txn_broadcasted.borrow_mut().drain(..) { + pending_txs.push(tx); + } + for tx in broadcast_b.txn_broadcasted.borrow_mut().drain(..) { + pending_txs.push(tx); + } + for tx in broadcast_c.txn_broadcasted.borrow_mut().drain(..) { + pending_txs.push(tx); + } + pending_txs.sort_by_key(|tx| tx.lock_time.to_consensus_u32()); + let mut deferred_txs = pending_txs; + loop { + let mut next_deferred_txs = Vec::new(); + let mut progressed = false; + for tx in deferred_txs { + if confirm_tx_and_sync_wallets(&mut chain_state, tx.clone()) { + found = true; + progressed = true; + } else { + next_deferred_txs.push(tx); + } + } + if !progressed { + // Keep only transactions that could become + // valid later, e.g. children waiting on their + // parent or timelocked claims waiting on height. + // Permanently stale conflicting spends should be + // dropped here or they create fake non-quiescence. + deferred_txs = next_deferred_txs + .into_iter() + .filter(|tx| should_retry_confirm_later(&chain_state, tx)) + .collect(); + break; + } + deferred_txs = next_deferred_txs; + } + if !deferred_txs.is_empty() { + broadcast_a + .txn_broadcasted + .borrow_mut() + .extend(deferred_txs.into_iter()); + } + if !found { + break; + } + assert!( + confirm_iter < 31, + "flush_progress tx confirmation loop failed to quiesce: {}", + pending_work_summary!() + ); + had_new_txs = true; + sync_with_chain_state( + &chain_state, + &nodes[0], + &monitor_a, + &mut node_height_a, + None, + ); + sync_with_chain_state( + &chain_state, + &nodes[1], + &monitor_b, + &mut node_height_b, + None, + ); + sync_with_chain_state( + &chain_state, + &nodes[2], + &monitor_c, + &mut node_height_c, + None, + ); + } + if completed_monitor_update || had_new_txs || had_msg_or_ev { + last_pass_no_updates = false; + continue; + } + if last_pass_no_updates { + break; + } + last_pass_no_updates = true; + } + assert!( + !has_pending_work!() || last_pass_no_updates, + "flush_progress exhausted {} iterations without quiescing: {}", + $max_iters, + pending_work_summary!() + ); + assert!( + !has_pending_work!() || !last_pass_no_updates || $max_iters > 0, + "flush_progress made no progress: {}", + pending_work_summary!() + ); + }}; + } + + macro_rules! advance_chain_carefully { + ($num_blocks: expr) => {{ + for _ in 0..$num_blocks { + // The force-close corpus used to jump 50/100/200 blocks in + // one shot, which made timeout paths dominate simply because + // queued messages, monitor completions, and claim propagation + // never got a chance to run. Advancing one block at a time + // with a bounded drain on both sides keeps those races + // possible without making them the default outcome. + flush_progress!(32); + if !has_pending_work!() { + // Stop early once there is nothing left that another + // empty block could unlock. Blindly consuming the entire + // requested jump mostly adds noise. + break; + } + chain_state.advance_height(1); + flush_progress!(32); + if !has_pending_work!() { + break; + } + } + }}; + } + let v = get_slice!(1)[0]; out.locked_write(format!("READ A BYTE! HANDLING INPUT {:x}...........\n", v).as_bytes()); match v { @@ -2515,13 +3066,49 @@ pub fn do_test(data: &[u8], out: Out) { }, // Sync node by 1 block to cover confirmation of a transaction. - 0xa8 => sync_with_chain_state(&mut chain_state, &nodes[0], &mut node_height_a, Some(1)), - 0xa9 => sync_with_chain_state(&mut chain_state, &nodes[1], &mut node_height_b, Some(1)), - 0xaa => sync_with_chain_state(&mut chain_state, &nodes[2], &mut node_height_c, Some(1)), + 0xa8 => sync_with_chain_state( + &mut chain_state, + &nodes[0], + &monitor_a, + &mut node_height_a, + Some(1), + ), + 0xa9 => sync_with_chain_state( + &mut chain_state, + &nodes[1], + &monitor_b, + &mut node_height_b, + Some(1), + ), + 0xaa => sync_with_chain_state( + &mut chain_state, + &nodes[2], + &monitor_c, + &mut node_height_c, + Some(1), + ), // Sync node to chain tip to cover confirmation of a transaction post-reorg-risk. - 0xab => sync_with_chain_state(&mut chain_state, &nodes[0], &mut node_height_a, None), - 0xac => sync_with_chain_state(&mut chain_state, &nodes[1], &mut node_height_b, None), - 0xad => sync_with_chain_state(&mut chain_state, &nodes[2], &mut node_height_c, None), + 0xab => sync_with_chain_state( + &mut chain_state, + &nodes[0], + &monitor_a, + &mut node_height_a, + None, + ), + 0xac => sync_with_chain_state( + &mut chain_state, + &nodes[1], + &monitor_b, + &mut node_height_b, + None, + ), + 0xad => sync_with_chain_state( + &mut chain_state, + &nodes[2], + &monitor_c, + &mut node_height_c, + None, + ), 0xb0 | 0xb1 | 0xb2 => { // Restart node A, picking among the in-flight `ChannelMonitor`s to use based on From 94c48752dbdc66a2f3ec2d7567973e506ee7c5bb Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Thu, 16 Apr 2026 14:23:14 +0200 Subject: [PATCH 06/12] fuzz: add force-close execution to chanmon_consistency --- fuzz/src/chanmon_consistency.rs | 683 +++++++++++++++++++++++++++----- 1 file changed, 590 insertions(+), 93 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index a4d21a80502..25739d94ca5 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -15,8 +15,8 @@ //! actions such as sending payments, handling events, or changing monitor update return values on //! a per-node basis. This should allow it to find any cases where the ordering of actions results //! in us getting out of sync with ourselves, and, assuming at least one of our recieve- or -//! send-side handling is correct, other peers. We consider it a failure if any action results in a -//! channel being force-closed. +//! send-side handling is correct, other peers. The fuzzer also exercises user-initiated +//! force-closes with on-chain commitment transaction confirmation. use bitcoin::amount::Amount; use bitcoin::constants::genesis_block; @@ -552,12 +552,12 @@ impl SignerProvider for KeyProvider { } } -// Since this fuzzer is only concerned with live-channel operations, we don't need to worry about -// any signer operations that come after a force close. -const SUPPORTED_SIGNER_OPS: [SignerOp; 3] = [ +const SUPPORTED_SIGNER_OPS: [SignerOp; 5] = [ SignerOp::SignCounterpartyCommitment, SignerOp::GetPerCommitmentPoint, SignerOp::ReleaseCommitmentSecret, + SignerOp::SignHolderCommitment, + SignerOp::SignHolderHtlcTransaction, ]; impl KeyProvider { @@ -1017,6 +1017,11 @@ pub fn do_test(data: &[u8], out: Out) { let mut config = UserConfig::default(); config.channel_config.forwarding_fee_proportional_millionths = 0; config.channel_handshake_config.announce_for_forwarding = true; + // The harness mutates open flows aggressively and may create + // force-close scenarios from channels that would fail the normal + // "prefer announced channels" policy check. Keep that check off + // here or we lose coverage before any close logic executes. + config.channel_handshake_limits.force_announced_channel_preference = false; config.reject_inbound_splices = false; match chan_type { ChanType::Legacy => { @@ -1080,6 +1085,11 @@ pub fn do_test(data: &[u8], out: Out) { let mut config = UserConfig::default(); config.channel_config.forwarding_fee_proportional_millionths = 0; config.channel_handshake_config.announce_for_forwarding = true; + // Restarts must preserve the same relaxed open policy as fresh + // nodes. Otherwise replayed peers can refuse the same channels that + // were already open before the restart and the harness diverges for + // reasons unrelated to force-close handling. + config.channel_handshake_limits.force_announced_channel_preference = false; config.reject_inbound_splices = false; match chan_type { ChanType::Legacy => { @@ -1124,9 +1134,10 @@ pub fn do_test(data: &[u8], out: Out) { // Update the latest `ChannelMonitor` state to match what we just told LDK. prev_state.persisted_monitor = serialized_mon; prev_state.persisted_monitor_id = mon_id; - // Wipe any `ChannelMonitor`s which we never told LDK we finished persisting, - // considering them discarded. LDK should replay these for us as they're stored in - // the `ChannelManager`. + // Drop any newer monitor blobs that the harness never marked as + // durably persisted. Restart testing depends on LDK replaying + // those updates from the `ChannelManager`; if we keep them here we + // stop exercising the stale-monitor recovery path entirely. prev_state.pending_monitors.clear(); chain_monitor.latest_monitors.lock().unwrap().insert(channel_id, prev_state); } @@ -1204,22 +1215,18 @@ pub fn do_test(data: &[u8], out: Out) { } macro_rules! make_channel { ($source: expr, $dest: expr, $source_monitor: expr, $dest_monitor: expr, $dest_keys_manager: expr, $chan_id: expr, $trusted_open: expr, $trusted_accept: expr) => {{ - if $trusted_open { - $source - .create_channel_to_trusted_peer_0reserve( - $dest.get_our_node_id(), - 100_000, - 42, - 0, - None, - None, - ) - .unwrap(); + let user_config = if $trusted_open { + let mut user_config = UserConfig::default(); + user_config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = false; + user_config.channel_handshake_config.announce_for_forwarding = false; + user_config.channel_handshake_limits.trust_own_funding_0conf = true; + Some(user_config) } else { - $source - .create_channel($dest.get_our_node_id(), 100_000, 42, 0, None, None) - .unwrap(); - } + None + }; + $source + .create_channel($dest.get_our_node_id(), 100_000, 42, 0, None, user_config) + .unwrap(); let open_channel = { let events = $source.get_and_clear_pending_msg_events(); assert_eq!(events.len(), 1); @@ -1309,6 +1316,131 @@ pub fn do_test(data: &[u8], out: Out) { } } + let funding_created = { + let events = $source.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + if let MessageSendEvent::SendFundingCreated { ref msg, .. } = events[0] { + msg.clone() + } else { + panic!("Wrong event type"); + } + }; + $dest.handle_funding_created($source.get_our_node_id(), &funding_created); + complete_all_pending_monitor_updates!($dest_monitor); + + let (funding_signed, channel_id) = { + let events = $dest.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + if let MessageSendEvent::SendFundingSigned { ref msg, .. } = events[0] { + (msg.clone(), msg.channel_id.clone()) + } else { + panic!("Wrong event type"); + } + }; + let events = $dest.get_and_clear_pending_events(); + assert_eq!(events.len(), 1); + if let events::Event::ChannelPending { ref counterparty_node_id, .. } = events[0] { + assert_eq!(counterparty_node_id, &$source.get_our_node_id()); + } else { + panic!("Wrong event type"); + } + + $source.handle_funding_signed($dest.get_our_node_id(), &funding_signed); + complete_all_pending_monitor_updates!($source_monitor); + + let events = $source.get_and_clear_pending_events(); + assert_eq!(events.len(), 1); + if let events::Event::ChannelPending { + ref counterparty_node_id, + channel_id: ref event_channel_id, + .. + } = events[0] + { + assert_eq!(counterparty_node_id, &$dest.get_our_node_id()); + assert_eq!(*event_channel_id, channel_id); + } else { + panic!("Wrong event type"); + } + }}; + ($source: expr, $dest: expr, $source_monitor: expr, $dest_monitor: expr, $dest_keys_manager: expr, $chan_id: expr) => {{ + $source.create_channel($dest.get_our_node_id(), 100_000, 42, 0, None, None).unwrap(); + let open_channel = { + let events = $source.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + if let MessageSendEvent::SendOpenChannel { ref msg, .. } = events[0] { + msg.clone() + } else { + panic!("Wrong event type"); + } + }; + + $dest.handle_open_channel($source.get_our_node_id(), &open_channel); + let accept_channel = { + let events = $dest.get_and_clear_pending_events(); + assert_eq!(events.len(), 1); + if let events::Event::OpenChannelRequest { + ref temporary_channel_id, + ref counterparty_node_id, + .. + } = events[0] + { + let mut random_bytes = [0u8; 16]; + random_bytes + .copy_from_slice(&$dest_keys_manager.get_secure_random_bytes()[..16]); + let user_channel_id = u128::from_be_bytes(random_bytes); + $dest + .accept_inbound_channel( + temporary_channel_id, + counterparty_node_id, + user_channel_id, + None, + ) + .unwrap(); + } else { + panic!("Wrong event type"); + } + let events = $dest.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + if let MessageSendEvent::SendAcceptChannel { ref msg, .. } = events[0] { + msg.clone() + } else { + panic!("Wrong event type"); + } + }; + + $source.handle_accept_channel($dest.get_our_node_id(), &accept_channel); + { + let mut events = $source.get_and_clear_pending_events(); + assert_eq!(events.len(), 1); + if let events::Event::FundingGenerationReady { + temporary_channel_id, + channel_value_satoshis, + output_script, + .. + } = events.pop().unwrap() + { + let tx = Transaction { + version: Version($chan_id), + lock_time: LockTime::ZERO, + input: Vec::new(), + output: vec![TxOut { + value: Amount::from_sat(channel_value_satoshis), + script_pubkey: output_script, + }], + }; + $source + .funding_transaction_generated( + temporary_channel_id, + $dest.get_our_node_id(), + tx.clone(), + ) + .unwrap(); + chain_state.confirm_tx(tx); + } else { + panic!("Wrong event type"); + } + } + let funding_created = { let events = $source.get_and_clear_pending_msg_events(); assert_eq!(events.len(), 1); @@ -1374,7 +1506,7 @@ pub fn do_test(data: &[u8], out: Out) { } } } else { - panic!("Wrong event type"); + panic!("Wrong event type in first lock_fundings pass: {:?}", event); } } } @@ -1583,6 +1715,7 @@ pub fn do_test(data: &[u8], out: Out) { let claimed_payment_hashes: RefCell> = RefCell::new(HashSet::new()); let payment_preimages: RefCell> = RefCell::new(new_hash_map()); + let closed_channels: RefCell> = RefCell::new(HashSet::new()); macro_rules! test_return { () => {{ @@ -1590,11 +1723,10 @@ pub fn do_test(data: &[u8], out: Out) { assert!(nodes[1].list_channels().len() <= 6); assert!(nodes[2].list_channels().len() <= 3); - // All broadcasters should be empty (all broadcast transactions should be handled - // explicitly). - assert!(broadcast_a.txn_broadcasted.borrow().is_empty()); - assert!(broadcast_b.txn_broadcasted.borrow().is_empty()); - assert!(broadcast_c.txn_broadcasted.borrow().is_empty()); + // Drain broadcasters since force-closes produce commitment transactions. + broadcast_a.txn_broadcasted.borrow_mut().clear(); + broadcast_b.txn_broadcasted.borrow_mut().clear(); + broadcast_c.txn_broadcasted.borrow_mut().clear(); return; }}; @@ -2275,10 +2407,12 @@ pub fn do_test(data: &[u8], out: Out) { } }, events::Event::SpliceFailed { .. } => {}, - events::Event::DiscardFunding { - funding_info: events::FundingInfo::Contribution { .. }, - .. - } => {}, + events::Event::ChannelClosed { channel_id, .. } => { + closed_channels.borrow_mut().insert(channel_id); + }, + events::Event::DiscardFunding { .. } => {}, + events::Event::SpendableOutputs { .. } => {}, + events::Event::BumpTransaction(..) => {}, _ => panic!("Unhandled event"), } @@ -3199,55 +3333,137 @@ pub fn do_test(data: &[u8], out: Out) { }, 0xc4 => { keys_manager_b.enable_op_for_all_signers(SignerOp::SignCounterpartyCommitment); - let filter = Some((nodes[0].get_our_node_id(), chan_a_id)); - nodes[1].signer_unblocked(filter); + nodes[1].signer_unblocked(None); }, 0xc5 => { - keys_manager_b.enable_op_for_all_signers(SignerOp::SignCounterpartyCommitment); - let filter = Some((nodes[2].get_our_node_id(), chan_b_id)); - nodes[1].signer_unblocked(filter); - }, - 0xc6 => { keys_manager_c.enable_op_for_all_signers(SignerOp::SignCounterpartyCommitment); nodes[2].signer_unblocked(None); }, - 0xc7 => { + 0xc6 => { keys_manager_a.enable_op_for_all_signers(SignerOp::GetPerCommitmentPoint); nodes[0].signer_unblocked(None); }, - 0xc8 => { - keys_manager_b.enable_op_for_all_signers(SignerOp::GetPerCommitmentPoint); - let filter = Some((nodes[0].get_our_node_id(), chan_a_id)); - nodes[1].signer_unblocked(filter); - }, - 0xc9 => { + 0xc7 => { keys_manager_b.enable_op_for_all_signers(SignerOp::GetPerCommitmentPoint); - let filter = Some((nodes[2].get_our_node_id(), chan_b_id)); - nodes[1].signer_unblocked(filter); + nodes[1].signer_unblocked(None); }, - 0xca => { + 0xc8 => { keys_manager_c.enable_op_for_all_signers(SignerOp::GetPerCommitmentPoint); nodes[2].signer_unblocked(None); }, - 0xcb => { + 0xc9 => { keys_manager_a.enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret); nodes[0].signer_unblocked(None); }, - 0xcc => { + 0xca => { keys_manager_b.enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret); - let filter = Some((nodes[0].get_our_node_id(), chan_a_id)); - nodes[1].signer_unblocked(filter); + nodes[1].signer_unblocked(None); + }, + 0xcb => { + keys_manager_c.enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret); + nodes[2].signer_unblocked(None); + }, + 0xcc => { + keys_manager_a.enable_op_for_all_signers(SignerOp::SignHolderCommitment); + nodes[0].signer_unblocked(None); }, 0xcd => { - keys_manager_b.enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret); - let filter = Some((nodes[2].get_our_node_id(), chan_b_id)); - nodes[1].signer_unblocked(filter); + keys_manager_b.enable_op_for_all_signers(SignerOp::SignHolderCommitment); + nodes[1].signer_unblocked(None); }, 0xce => { - keys_manager_c.enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret); + keys_manager_c.enable_op_for_all_signers(SignerOp::SignHolderCommitment); + nodes[2].signer_unblocked(None); + }, + 0xcf => { + keys_manager_a.enable_op_for_all_signers(SignerOp::SignHolderHtlcTransaction); + keys_manager_b.enable_op_for_all_signers(SignerOp::SignHolderHtlcTransaction); + keys_manager_c.enable_op_for_all_signers(SignerOp::SignHolderHtlcTransaction); + nodes[0].signer_unblocked(None); + nodes[1].signer_unblocked(None); nodes[2].signer_unblocked(None); }, + // User-triggered force-close opcodes. These are only the start of + // the interesting behavior: they enqueue commitment broadcasts, + // monitor updates, close events, and later HTLC claims. The settle + // opcode below is what drives those consequences to quiescence. + 0xd0 => { + if nodes[0] + .force_close_broadcasting_latest_txn( + &chan_a_id, + &nodes[1].get_our_node_id(), + "]]]]]]]]".to_string(), + ) + .is_ok() + { + closed_channels.borrow_mut().insert(chan_a_id); + } + }, + 0xd1 => { + if nodes[1] + .force_close_broadcasting_latest_txn( + &chan_b_id, + &nodes[2].get_our_node_id(), + "]]]]]]]".to_string(), + ) + .is_ok() + { + closed_channels.borrow_mut().insert(chan_b_id); + } + }, + 0xd2 => { + if nodes[1] + .force_close_broadcasting_latest_txn( + &chan_a_id, + &nodes[0].get_our_node_id(), + "]]]]]]".to_string(), + ) + .is_ok() + { + closed_channels.borrow_mut().insert(chan_a_id); + } + }, + 0xd3 => { + if nodes[2] + .force_close_broadcasting_latest_txn( + &chan_b_id, + &nodes[1].get_our_node_id(), + "]]]]]".to_string(), + ) + .is_ok() + { + closed_channels.borrow_mut().insert(chan_b_id); + } + }, + + // Low-level confirmation helpers. These are intentionally blunt; + // they let the fuzzer force individual broadcasters onto chain, + // while `0xff` performs the more realistic multi-round settle. + 0xd8 => { + for tx in broadcast_a.txn_broadcasted.borrow_mut().drain(..) { + confirm_tx_and_sync_wallets(&mut chain_state, tx); + } + }, + 0xd9 => { + for tx in broadcast_b.txn_broadcasted.borrow_mut().drain(..) { + confirm_tx_and_sync_wallets(&mut chain_state, tx); + } + }, + 0xda => { + for tx in broadcast_c.txn_broadcasted.borrow_mut().drain(..) { + confirm_tx_and_sync_wallets(&mut chain_state, tx); + } + }, + + // Large synthetic height jumps. These exist so the corpus can + // reach CLTV and CSV-based force-close resolution states, but the + // helper above keeps them from becoming unrealistic "skip the + // network and time everything out" buttons. + 0xdc => advance_chain_carefully!(50), + 0xdd => advance_chain_carefully!(100), + 0xde => advance_chain_carefully!(200), + 0xf0 => { for id in &chan_ab_ids { complete_monitor_update(&monitor_a, id, &complete_first); @@ -3357,45 +3573,219 @@ pub fn do_test(data: &[u8], out: Out) { nodes[1].signer_unblocked(None); nodes[2].signer_unblocked(None); + // Restarts may intentionally reload an older persisted monitor. + // Before settling, catch those monitors up to the harness' + // current height without rewinding the `ChannelManager`. This + // preserves the stale-monitor replay scenario while avoiding a + // fake reorg inside the node itself. + for (monitor, node_height) in [ + (&monitor_a, &node_height_a), + (&monitor_b, &node_height_b), + (&monitor_c, &node_height_c), + ] { + let mut min_monitor_height = *node_height; + for chan_id in monitor.chain_monitor.list_monitors() { + if let Ok(mon) = monitor.chain_monitor.get_monitor(chan_id) { + min_monitor_height = + std::cmp::min(min_monitor_height, mon.current_best_block().height); + } + } + let mut h = min_monitor_height; + while h < *node_height { + let mut next_height = h + 1; + while next_height <= *node_height + && chain_state.block_at(next_height).1.is_empty() + { + next_height += 1; + } + if next_height > *node_height { + h = *node_height; + let (header, _) = chain_state.block_at(h); + monitor.chain_monitor.best_block_updated(header, h); + break; + } + if next_height > h + 1 { + h = next_height - 1; + let (header, _) = chain_state.block_at(h); + monitor.chain_monitor.best_block_updated(header, h); + } + h = next_height; + let (header, txn) = chain_state.block_at(h); + let txdata: Vec<_> = + txn.iter().enumerate().map(|(i, tx)| (i + 1, tx)).collect(); + if !txdata.is_empty() { + monitor.chain_monitor.transactions_confirmed(header, &txdata, h); + } + monitor.chain_monitor.best_block_updated(header, h); + } + } + macro_rules! process_all_events { - () => { { + () => {{ + let mut settled = false; let mut last_pass_no_updates = false; - for i in 0..std::usize::MAX { - if i == 100 { - panic!("It may take may iterations to settle the state, but it should not take forever"); - } - // Next, make sure no monitor updates are pending + for i in 0..100 { + // Settle in rounds: first unblock monitors, then let + // nodes emit messages and events, then confirm any + // transactions they broadcast. Force-close flows need + // all three because each step may unlock the next. + let mut completed_monitor_update = false; for id in &chan_ab_ids { - complete_all_monitor_updates(&monitor_a, id); - complete_all_monitor_updates(&monitor_b, id); + completed_monitor_update |= complete_all_monitor_updates(&monitor_a, id); + completed_monitor_update |= complete_all_monitor_updates(&monitor_b, id); } for id in &chan_bc_ids { - complete_all_monitor_updates(&monitor_b, id); - complete_all_monitor_updates(&monitor_c, id); + completed_monitor_update |= complete_all_monitor_updates(&monitor_b, id); + completed_monitor_update |= complete_all_monitor_updates(&monitor_c, id); } - // Then, make sure any current forwards make their way to their destination - if process_msg_events!(0, false, ProcessMessages::AllMessages) { - last_pass_no_updates = false; - continue; - } - if process_msg_events!(1, false, ProcessMessages::AllMessages) { - last_pass_no_updates = false; - continue; - } - if process_msg_events!(2, false, ProcessMessages::AllMessages) { - last_pass_no_updates = false; - continue; - } - // ...making sure any payments are claimed. - if process_events!(0, false) { - last_pass_no_updates = false; - continue; - } - if process_events!(1, false) { - last_pass_no_updates = false; - continue; - } - if process_events!(2, false) { + // Process messages and events first so nodes + // learn preimages before on-chain txs are + // confirmed. A monitor that already has a + // preimage when it sees the counterparty + // commitment tx will broadcast the claim + // directly, but one that sees the commitment + // tx first and only later gets the preimage + // will not re-check for claimable outputs. + let mut had_msg_or_ev = false; + let node0_msgs = process_msg_events!(0, false, ProcessMessages::AllMessages); + if node0_msgs { + had_msg_or_ev = true; + } + let node1_msgs = process_msg_events!(1, false, ProcessMessages::AllMessages); + if node1_msgs { + had_msg_or_ev = true; + } + let node2_msgs = process_msg_events!(2, false, ProcessMessages::AllMessages); + if node2_msgs { + had_msg_or_ev = true; + } + let node0_evs = process_events!(0, false); + if node0_evs { + had_msg_or_ev = true; + } + let node1_evs = process_events!(1, false); + if node1_evs { + had_msg_or_ev = true; + } + let node2_evs = process_events!(2, false); + if node2_evs { + had_msg_or_ev = true; + } + // Sync nodes to chain tip after messages so + // monitors that just received preimages can + // generate claims when they see commitment txs. + sync_with_chain_state(&chain_state, &nodes[0], &monitor_a, &mut node_height_a, None); + sync_with_chain_state(&chain_state, &nodes[1], &monitor_b, &mut node_height_b, None); + sync_with_chain_state(&chain_state, &nodes[2], &monitor_c, &mut node_height_c, None); + // Process chain monitor events (BumpTransaction, + // SpendableOutputs). + { + let monitors = [&monitor_a, &monitor_b, &monitor_c]; + let broadcasters: [&Arc; 3] = [&broadcast_a, &broadcast_b, &broadcast_c]; + let keys_managers = [&keys_manager_a, &keys_manager_b, &keys_manager_c]; + for (idx, monitor) in monitors.iter().enumerate() { + let wallet = WalletSync::new( + &wallets[idx], + Arc::clone(&loggers[idx]), + ); + let handler = BumpTransactionEventHandlerSync::new( + broadcasters[idx].as_ref(), + &wallet, + keys_managers[idx].as_ref(), + Arc::clone(&loggers[idx]), + ); + let broadcaster = broadcasters[idx]; + monitor.chain_monitor.process_pending_events( + &|event: events::Event| { + if let events::Event::BumpTransaction(ref bump) = event { + match bump { + events::bump_transaction::BumpTransactionEvent::ChannelClose { + commitment_tx, + channel_id, + counterparty_node_id, + .. + } => { + broadcaster.broadcast_transactions(&[( + commitment_tx, + lightning::chain::chaininterface::TransactionType::UnilateralClose { + counterparty_node_id: *counterparty_node_id, + channel_id: *channel_id, + }, + )]); + }, + events::bump_transaction::BumpTransactionEvent::HTLCResolution { .. } => { + handler.handle_event(bump); + }, + } + } + Ok(()) + }, + ); + } + } + let mut had_new_txs = false; + for confirm_iter in 0..32 { + let mut found = false; + let mut pending_txs: Vec = Vec::new(); + for tx in broadcast_a.txn_broadcasted.borrow_mut().drain(..) { + pending_txs.push(tx); + } + for tx in broadcast_b.txn_broadcasted.borrow_mut().drain(..) { + pending_txs.push(tx); + } + for tx in broadcast_c.txn_broadcasted.borrow_mut().drain(..) { + pending_txs.push(tx); + } + // Sort by lock_time so preimage claims + // (lock_time 0) confirm before timeout txs + // (lock_time = cltv_expiry) when both spend + // the same HTLC output in the same drain + // round. The lock_time consensus check in + // confirm_tx handles the case where the + // timeout's lock_time hasn't been reached, + // but when the chain height is past both + // lock_times, this sort is the tiebreaker. + pending_txs.sort_by_key(|tx| tx.lock_time.to_consensus_u32()); + let mut deferred_txs = pending_txs; + loop { + let mut next_deferred_txs = Vec::new(); + let mut progressed = false; + for tx in deferred_txs { + if confirm_tx_and_sync_wallets(&mut chain_state, tx.clone()) { + found = true; + progressed = true; + } else { + next_deferred_txs.push(tx); + } + } + if !progressed { + deferred_txs = next_deferred_txs + .into_iter() + .filter(|tx| should_retry_confirm_later(&chain_state, tx)) + .collect(); + break; + } + deferred_txs = next_deferred_txs; + } + if !deferred_txs.is_empty() { + broadcast_a.txn_broadcasted.borrow_mut().extend( + deferred_txs.into_iter() + ); + } + if !found { + break; + } + assert!( + confirm_iter < 31, + "process_all_events tx confirmation loop failed to quiesce at settle iter {i}: {}", + pending_work_summary!() + ); + had_new_txs = true; + sync_with_chain_state(&chain_state, &nodes[0], &monitor_a, &mut node_height_a, None); + sync_with_chain_state(&chain_state, &nodes[1], &monitor_b, &mut node_height_b, None); + sync_with_chain_state(&chain_state, &nodes[2], &monitor_c, &mut node_height_c, None); + } + if completed_monitor_update || had_new_txs || had_msg_or_ev { last_pass_no_updates = false; continue; } @@ -3407,10 +3797,16 @@ pub fn do_test(data: &[u8], out: Out) { // // Thus, we only exit if we manage two iterations with no messages // or events to process. + settled = true; break; } last_pass_no_updates = true; } + assert!( + settled, + "process_all_events exceeded settle budget: {}", + pending_work_summary!() + ); } }; } @@ -3424,13 +3820,71 @@ pub fn do_test(data: &[u8], out: Out) { } process_all_events!(); + let reconcile_pending_payments = || { + for node_idx in 0..nodes.len() { + let active_pending = nodes[node_idx] + .list_recent_payments() + .into_iter() + .filter_map(|payment| match payment { + RecentPaymentDetails::Pending { payment_id, .. } => { + Some(payment_id) + }, + _ => None, + }) + .collect::>(); + // Restarts rebuild outbound-payment state from the live + // ChannelManager. Drop any ids that remain only in the + // harness cache so the final invariant checks reflect the + // node state we are actually exercising. + pending_payments.borrow_mut()[node_idx] + .retain(|payment_id| active_pending.contains(payment_id)); + } + }; + + // If any channels were force-closed, advance chain height until HTLC + // resolution activity quiesces. We keep this bounded to avoid hangs, + // but allow more rounds than the original fixed four-pass cleanup since + // RBF bumps and confirmation delays can legitimately require additional + // chain progress before a sender sees a terminal payment event. + if !closed_channels.borrow().is_empty() { + let open_channels = nodes[0] + .list_channels() + .iter() + .chain(nodes[1].list_channels().iter()) + .chain(nodes[2].list_channels().iter()) + .cloned() + .collect::>(); + let open_refs: Vec<&_> = open_channels.iter().collect(); + for _ in 0..64 { + let requires_timelock_resolution = + [&monitor_a, &monitor_b, &monitor_c].iter().any(|monitor| { + has_timed_balance( + &monitor.chain_monitor.get_claimable_balances(&open_refs), + ) + }); + reconcile_pending_payments(); + let has_pending_payments = + pending_payments.borrow().iter().any(|payments| !payments.is_empty()); + let requires_timelock_resolution = + requires_timelock_resolution || has_pending_payments; + if !requires_timelock_resolution { + break; + } + chain_state.advance_height(250); + process_all_events!(); + } + } + // Verify no payments are stuck - all should have resolved + reconcile_pending_payments(); for (idx, pending) in pending_payments.borrow().iter().enumerate() { assert!( pending.is_empty(), - "Node {} has {} stuck pending payments after settling all state", + "Node {} has {} stuck pending payments after settling all state: ids={:?}; {}", idx, - pending.len() + pending.len(), + pending, + pending_work_summary!(), ); } @@ -3450,18 +3904,61 @@ pub fn do_test(data: &[u8], out: Out) { // Finally, make sure that at least one end of each channel can make a substantial payment for &chan_id in &chan_ab_ids { + if closed_channels.borrow().contains(&chan_id) { + continue; + } assert!( send(0, 1, chan_id, 10_000_000, &mut p_ctr) || send(1, 0, chan_id, 10_000_000, &mut p_ctr) ); } for &chan_id in &chan_bc_ids { + if closed_channels.borrow().contains(&chan_id) { + continue; + } assert!( send(1, 2, chan_id, 10_000_000, &mut p_ctr) || send(2, 1, chan_id, 10_000_000, &mut p_ctr) ); } + // After settlement, verify that closed channels have no + // ClaimableOnChannelClose balances (which would indicate the + // monitor still thinks the channel is open). + if !closed_channels.borrow().is_empty() { + let open_channels = nodes[0] + .list_channels() + .iter() + .chain(nodes[1].list_channels().iter()) + .chain(nodes[2].list_channels().iter()) + .map(|c| c.clone()) + .collect::>(); + let open_refs: Vec<&_> = open_channels.iter().collect(); + for (label, monitor) in + [("A", &monitor_a), ("B", &monitor_b), ("C", &monitor_c)] + { + let balances = monitor.chain_monitor.get_claimable_balances(&open_refs); + for balance in &balances { + if matches!(balance, Balance::ClaimableOnChannelClose { .. }) { + panic!( + "Monitor {} has ClaimableOnChannelClose balance after settlement: {:?}", + label, balance + ); + } + } + if !balances.is_empty() { + out.locked_write( + format!( + "Monitor {} has {} remaining balances after settlement.\n", + label, + balances.len() + ) + .as_bytes(), + ); + } + } + } + last_htlc_clear_fee_a = fee_est_a.ret_val.load(atomic::Ordering::Acquire); last_htlc_clear_fee_b = fee_est_b.ret_val.load(atomic::Ordering::Acquire); last_htlc_clear_fee_c = fee_est_c.ret_val.load(atomic::Ordering::Acquire); From 561d878c386ee7c493e2724c02747e8914deaf95 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Fri, 17 Apr 2026 15:20:33 +0200 Subject: [PATCH 07/12] fuzz: more fc [squash] --- fuzz/.gitignore | 1 + fuzz/src/chanmon_consistency.rs | 733 ++++++++++++++++++-------------- 2 files changed, 411 insertions(+), 323 deletions(-) diff --git a/fuzz/.gitignore b/fuzz/.gitignore index e8dc6b6e08b..cc3f5f53040 100644 --- a/fuzz/.gitignore +++ b/fuzz/.gitignore @@ -2,3 +2,4 @@ hfuzz_target target hfuzz_workspace corpus +artifacts \ No newline at end of file diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 25739d94ca5..22cdbfa726d 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -952,22 +952,6 @@ pub fn do_test(data: &[u8], out: Out) { let mut node_height_a: u32 = 0; let mut node_height_b: u32 = 0; let mut node_height_c: u32 = 0; - let has_timed_balance = |balances: &[Balance]| { - balances.iter().any(|balance| { - matches!( - balance, - // Force-close settlement only needs to stretch height when - // there is still time-sensitive work that may release new - // on-chain claims. `ClaimableAwaitingConfirmations` is not in - // that bucket, it means some transaction already exists and we - // are mostly waiting for more confirms on it. - Balance::ContentiousClaimable { .. } - | Balance::MaybeTimeoutClaimableHTLC { .. } - | Balance::MaybePreimageClaimableHTLC { .. } - | Balance::CounterpartyRevokedOutputClaimable { .. } - ) - }) - }; let summarize_balances = |balances: &[Balance]| -> String { let mut on_close = 0; let mut awaiting = 0; @@ -1710,12 +1694,44 @@ pub fn do_test(data: &[u8], out: Out) { let mut node_c_ser = nodes[2].encode(); let pending_payments = RefCell::new([Vec::new(), Vec::new(), Vec::new()]); - let resolved_payments: RefCell<[HashMap>; 3]> = - RefCell::new([new_hash_map(), new_hash_map(), new_hash_map()]); + let resolved_payment_ids: RefCell<[HashSet; 3]> = + RefCell::new([HashSet::new(), HashSet::new(), HashSet::new()]); let claimed_payment_hashes: RefCell> = RefCell::new(HashSet::new()); + let receiver_claimed_payment_hashes: RefCell> = + RefCell::new(HashSet::new()); + let sender_sent_payment_hashes: RefCell> = RefCell::new(HashSet::new()); let payment_preimages: RefCell> = RefCell::new(new_hash_map()); let closed_channels: RefCell> = RefCell::new(HashSet::new()); + let summarize_claim_tracking = || -> String { + let claim_requested = claimed_payment_hashes.borrow(); + let receiver_claimed = receiver_claimed_payment_hashes.borrow(); + let sender_sent = sender_sent_payment_hashes.borrow(); + let missing_receiver = + claim_requested.iter().filter(|hash| !receiver_claimed.contains(*hash)).count(); + let missing_sender = + claim_requested.iter().filter(|hash| !sender_sent.contains(*hash)).count(); + format!( + "claims requested={} receiver_claimed={} sender_sent={} missing_receiver={} missing_sender={}", + claim_requested.len(), + receiver_claimed.len(), + sender_sent.len(), + missing_receiver, + missing_sender, + ) + }; + let has_unfinished_claims = || { + let claim_requested = claimed_payment_hashes.borrow(); + let receiver_claimed = receiver_claimed_payment_hashes.borrow(); + let sender_sent = sender_sent_payment_hashes.borrow(); + claim_requested + .iter() + .any(|hash| !receiver_claimed.contains(hash) || !sender_sent.contains(hash)) + }; + let has_live_payment_work = || { + pending_payments.borrow().iter().any(|payments| !payments.is_empty()) + || has_unfinished_claims() + }; macro_rules! test_return { () => {{ @@ -1744,6 +1760,22 @@ pub fn do_test(data: &[u8], out: Out) { }}; } + macro_rules! unblock_all_signers { + () => {{ + // Hard mode focuses on force-close outcomes. Once the harness starts + // auto-driving progress, signer stalls are no longer interesting on + // their own, so restore the supported ops and retry any blocked work. + for op in SUPPORTED_SIGNER_OPS { + keys_manager_a.enable_op_for_all_signers(op); + keys_manager_b.enable_op_for_all_signers(op); + keys_manager_c.enable_op_for_all_signers(op); + } + nodes[0].signer_unblocked(None); + nodes[1].signer_unblocked(None); + nodes[2].signer_unblocked(None); + }}; + } + let splice_channel = |node: &ChanMan, counterparty_node_id: &PublicKey, @@ -1902,6 +1934,8 @@ pub fn do_test(data: &[u8], out: Out) { MessageSendEvent::SendChannelReady { .. } => continue, MessageSendEvent::SendAnnouncementSignatures { .. } => continue, MessageSendEvent::BroadcastChannelUpdate { .. } => continue, + MessageSendEvent::BroadcastChannelAnnouncement { .. } => continue, + MessageSendEvent::BroadcastNodeAnnouncement { .. } => continue, MessageSendEvent::SendChannelUpdate { ref node_id, .. } => { if Some(*node_id) == expect_drop_id { panic!("peer_disconnected should drop msgs bound for the disconnected peer"); } *node_id == a_id @@ -2299,7 +2333,7 @@ pub fn do_test(data: &[u8], out: Out) { let mut events = nodes[$node].get_and_clear_pending_events(); let had_events = !events.is_empty(); let mut pending_payments = pending_payments.borrow_mut(); - let mut resolved_payments = resolved_payments.borrow_mut(); + let mut resolved_payment_ids = resolved_payment_ids.borrow_mut(); for event in events.drain(..) { match event { events::Event::PaymentClaimable { payment_hash, .. } => { @@ -2318,43 +2352,46 @@ pub fn do_test(data: &[u8], out: Out) { }, events::Event::PaymentSent { payment_id, payment_hash, .. } => { let sent_id = payment_id.unwrap(); - let idx_opt = - pending_payments[$node].iter().position(|id| *id == sent_id); - if let Some(idx) = idx_opt { - pending_payments[$node].remove(idx); - resolved_payments[$node].insert(sent_id, Some(payment_hash)); - } else { - assert!(resolved_payments[$node].contains_key(&sent_id)); - } - }, + let idx_opt = + pending_payments[$node].iter().position(|id| *id == sent_id); + if let Some(idx) = idx_opt { + pending_payments[$node].remove(idx); + resolved_payment_ids[$node].insert(sent_id); + sender_sent_payment_hashes.borrow_mut().insert(payment_hash); + } else { + assert!(resolved_payment_ids[$node].contains(&sent_id)); + } + }, // Even though we don't explicitly send probes, because probes are // detected based on hashing the payment hash+preimage, its rather // trivial for the fuzzer to build payments that accidentally end up // looking like probes. events::Event::ProbeSuccessful { payment_id, .. } => { - let idx_opt = - pending_payments[$node].iter().position(|id| *id == payment_id); - if let Some(idx) = idx_opt { - pending_payments[$node].remove(idx); - resolved_payments[$node].insert(payment_id, None); - } else { - assert!(resolved_payments[$node].contains_key(&payment_id)); - } - }, + let idx_opt = + pending_payments[$node].iter().position(|id| *id == payment_id); + if let Some(idx) = idx_opt { + pending_payments[$node].remove(idx); + resolved_payment_ids[$node].insert(payment_id); + } else { + assert!(resolved_payment_ids[$node].contains(&payment_id)); + } + }, events::Event::PaymentFailed { payment_id, .. } | events::Event::ProbeFailed { payment_id, .. } => { - let idx_opt = - pending_payments[$node].iter().position(|id| *id == payment_id); - if let Some(idx) = idx_opt { - pending_payments[$node].remove(idx); - resolved_payments[$node].insert(payment_id, None); - } else if !resolved_payments[$node].contains_key(&payment_id) { - // Payment failed immediately on send, so it was never added to - // pending_payments. Add it to resolved_payments to track it. - resolved_payments[$node].insert(payment_id, None); - } + let idx_opt = + pending_payments[$node].iter().position(|id| *id == payment_id); + if let Some(idx) = idx_opt { + pending_payments[$node].remove(idx); + resolved_payment_ids[$node].insert(payment_id); + } else if !resolved_payment_ids[$node].contains(&payment_id) { + // Payment failed immediately on send, so it was never added to + // pending_payments. Record its terminal payment_id anyway. + resolved_payment_ids[$node].insert(payment_id); + } + }, + events::Event::PaymentClaimed { payment_hash, .. } => { + receiver_claimed_payment_hashes.borrow_mut().insert(payment_hash); }, - events::Event::PaymentClaimed { .. } => {}, events::Event::PaymentPathSuccessful { .. } => {}, events::Event::PaymentPathFailed { .. } => {}, events::Event::PaymentForwarded { .. } if $node == 1 => {}, @@ -2363,11 +2400,12 @@ pub fn do_test(data: &[u8], out: Out) { failure_type: events::HTLCHandlingFailureType::Receive { payment_hash }, .. } => { - // The receiver failed to handle this HTLC (e.g., HTLC - // timeout won the race against the claim). Remove it from - // claimed hashes so we don't assert that the sender must - // have received PaymentSent. - claimed_payment_hashes.borrow_mut().remove(&payment_hash); + assert!( + !claimed_payment_hashes.borrow().contains(&payment_hash), + "Payment {:?} hit HTLCHandlingFailed::Receive after claim_funds: {}", + payment_hash, + pending_work_summary!(), + ); }, events::Event::HTLCHandlingFailed { .. } => {}, @@ -2472,6 +2510,9 @@ pub fn do_test(data: &[u8], out: Out) { let send = |source_idx: usize, dest_idx: usize, dest_chan_id, amt, payment_ctr: &mut u64| { + if closed_channels.borrow().contains(&dest_chan_id) { + return false; + } let source = &nodes[source_idx]; let dest = &nodes[dest_idx]; let (secret, hash) = get_payment_secret_hash(dest, payment_ctr, &payment_preimages); @@ -2494,6 +2535,11 @@ pub fn do_test(data: &[u8], out: Out) { dest_chan_id: ChannelId, amt: u64, payment_ctr: &mut u64| { + if closed_channels.borrow().contains(&middle_chan_id) + || closed_channels.borrow().contains(&dest_chan_id) + { + return; + } let source = &nodes[source_idx]; let middle = &nodes[middle_idx]; let dest = &nodes[dest_idx]; @@ -2522,12 +2568,21 @@ pub fn do_test(data: &[u8], out: Out) { dest_chan_ids: &[ChannelId], amt: u64, payment_ctr: &mut u64| { + let live_dest_chan_ids = dest_chan_ids + .iter() + .copied() + .filter(|chan_id| !closed_channels.borrow().contains(chan_id)) + .collect::>(); + if live_dest_chan_ids.is_empty() { + return; + } let source = &nodes[source_idx]; let dest = &nodes[dest_idx]; let (secret, hash) = get_payment_secret_hash(dest, payment_ctr, &payment_preimages); let mut id = PaymentId([0; 32]); id.0[0..8].copy_from_slice(&payment_ctr.to_ne_bytes()); - let succeeded = send_mpp_payment(source, dest, dest_chan_ids, amt, secret, hash, id); + let succeeded = + send_mpp_payment(source, dest, &live_dest_chan_ids, amt, secret, hash, id); if succeeded { pending_payments.borrow_mut()[source_idx].push(id); } @@ -2541,6 +2596,19 @@ pub fn do_test(data: &[u8], out: Out) { dest_chan_ids: &[ChannelId], amt: u64, payment_ctr: &mut u64| { + let live_middle_chan_ids = middle_chan_ids + .iter() + .copied() + .filter(|chan_id| !closed_channels.borrow().contains(chan_id)) + .collect::>(); + let live_dest_chan_ids = dest_chan_ids + .iter() + .copied() + .filter(|chan_id| !closed_channels.borrow().contains(chan_id)) + .collect::>(); + if live_middle_chan_ids.is_empty() || live_dest_chan_ids.is_empty() { + return; + } let source = &nodes[source_idx]; let middle = &nodes[middle_idx]; let dest = &nodes[dest_idx]; @@ -2550,9 +2618,9 @@ pub fn do_test(data: &[u8], out: Out) { let succeeded = send_mpp_hop_payment( source, middle, - middle_chan_ids, + &live_middle_chan_ids, dest, - dest_chan_ids, + &live_dest_chan_ids, amt, secret, hash, @@ -2589,7 +2657,9 @@ pub fn do_test(data: &[u8], out: Out) { monitor.chain_monitor.get_claimable_balances(&open_refs).iter().any(|balance| { matches!( balance, - Balance::ContentiousClaimable { .. } + Balance::ClaimableOnChannelClose { .. } + | Balance::ClaimableAwaitingConfirmations { .. } + | Balance::ContentiousClaimable { .. } | Balance::MaybeTimeoutClaimableHTLC { .. } | Balance::MaybePreimageClaimableHTLC { .. } | Balance::CounterpartyRevokedOutputClaimable { .. } @@ -2625,7 +2695,7 @@ pub fn do_test(data: &[u8], out: Out) { let balances_b = monitor_b.chain_monitor.get_claimable_balances(&open_refs); let balances_c = monitor_c.chain_monitor.get_claimable_balances(&open_refs); format!( - "queues ab={} ba={} bc={} cb={} bcast=({},{},{}) pending=({},{},{}) monitor_updates={} timed_work={} heights=({},{},{}) tip={} balances_a=[{}] balances_b=[{}] balances_c=[{}]", + "queues ab={} ba={} bc={} cb={} bcast=({},{},{}) pending=({},{},{}) monitor_updates={} timed_work={} heights=({},{},{}) tip={} {} balances_a=[{}] balances_b=[{}] balances_c=[{}]", ab_events.len(), ba_events.len(), bc_events.len(), @@ -2642,6 +2712,7 @@ pub fn do_test(data: &[u8], out: Out) { node_height_b, node_height_c, chain_state.tip_height(), + summarize_claim_tracking(), summarize_balances(&balances_a), summarize_balances(&balances_b), summarize_balances(&balances_c), @@ -2649,77 +2720,75 @@ pub fn do_test(data: &[u8], out: Out) { }}; } - macro_rules! flush_progress { - ($max_iters: expr) => {{ - let mut last_pass_no_updates = false; - for _ in 0..$max_iters { - let mut completed_monitor_update = false; - for id in &chan_ab_ids { - completed_monitor_update |= complete_all_monitor_updates(&monitor_a, id); - completed_monitor_update |= complete_all_monitor_updates(&monitor_b, id); - } - for id in &chan_bc_ids { - completed_monitor_update |= complete_all_monitor_updates(&monitor_b, id); - completed_monitor_update |= complete_all_monitor_updates(&monitor_c, id); - } - let mut had_msg_or_ev = false; - if process_msg_events!(0, false, ProcessMessages::AllMessages) { - had_msg_or_ev = true; - } - if process_msg_events!(1, false, ProcessMessages::AllMessages) { - had_msg_or_ev = true; - } - if process_msg_events!(2, false, ProcessMessages::AllMessages) { - had_msg_or_ev = true; - } - if process_events!(0, false) { - had_msg_or_ev = true; - } - if process_events!(1, false) { - had_msg_or_ev = true; - } - if process_events!(2, false) { - had_msg_or_ev = true; - } - sync_with_chain_state( - &chain_state, - &nodes[0], - &monitor_a, - &mut node_height_a, - None, - ); - sync_with_chain_state( - &chain_state, - &nodes[1], - &monitor_b, - &mut node_height_b, - None, - ); - sync_with_chain_state( - &chain_state, - &nodes[2], - &monitor_c, - &mut node_height_c, - None, - ); - { - let monitors = [&monitor_a, &monitor_b, &monitor_c]; - let broadcasters: [&Arc; 3] = - [&broadcast_a, &broadcast_b, &broadcast_c]; - let keys_managers = [&keys_manager_a, &keys_manager_b, &keys_manager_c]; - for (idx, monitor) in monitors.iter().enumerate() { - let wallet = WalletSync::new(&wallets[idx], Arc::clone(&loggers[idx])); - let handler = BumpTransactionEventHandlerSync::new( - broadcasters[idx].as_ref(), - &wallet, - keys_managers[idx].as_ref(), - Arc::clone(&loggers[idx]), - ); - let broadcaster = broadcasters[idx]; - monitor.chain_monitor.process_pending_events( - &|event: events::Event| { - if let events::Event::BumpTransaction(ref bump) = event { - match bump { + macro_rules! progress_round { + () => {{ + let mut completed_monitor_update = false; + for id in &chan_ab_ids { + completed_monitor_update |= complete_all_monitor_updates(&monitor_a, id); + completed_monitor_update |= complete_all_monitor_updates(&monitor_b, id); + } + for id in &chan_bc_ids { + completed_monitor_update |= complete_all_monitor_updates(&monitor_b, id); + completed_monitor_update |= complete_all_monitor_updates(&monitor_c, id); + } + let mut had_msg_or_ev = false; + if process_msg_events!(0, false, ProcessMessages::AllMessages) { + had_msg_or_ev = true; + } + if process_msg_events!(1, false, ProcessMessages::AllMessages) { + had_msg_or_ev = true; + } + if process_msg_events!(2, false, ProcessMessages::AllMessages) { + had_msg_or_ev = true; + } + if process_events!(0, false) { + had_msg_or_ev = true; + } + if process_events!(1, false) { + had_msg_or_ev = true; + } + if process_events!(2, false) { + had_msg_or_ev = true; + } + sync_with_chain_state( + &chain_state, + &nodes[0], + &monitor_a, + &mut node_height_a, + None, + ); + sync_with_chain_state( + &chain_state, + &nodes[1], + &monitor_b, + &mut node_height_b, + None, + ); + sync_with_chain_state( + &chain_state, + &nodes[2], + &monitor_c, + &mut node_height_c, + None, + ); + let mut had_new_txs = false; + { + let monitors = [&monitor_a, &monitor_b, &monitor_c]; + let broadcasters: [&Arc; 3] = + [&broadcast_a, &broadcast_b, &broadcast_c]; + let keys_managers = [&keys_manager_a, &keys_manager_b, &keys_manager_c]; + for (idx, monitor) in monitors.iter().enumerate() { + let wallet = WalletSync::new(&wallets[idx], Arc::clone(&loggers[idx])); + let handler = BumpTransactionEventHandlerSync::new( + broadcasters[idx].as_ref(), + &wallet, + keys_managers[idx].as_ref(), + Arc::clone(&loggers[idx]), + ); + let broadcaster = broadcasters[idx]; + monitor.chain_monitor.process_pending_events(&|event: events::Event| { + if let events::Event::BumpTransaction(ref bump) = event { + match bump { // Commitment transactions are already // fully assembled by LDK when a channel // closes, so the harness can broadcast @@ -2746,91 +2815,94 @@ pub fn do_test(data: &[u8], out: Out) { events::bump_transaction::BumpTransactionEvent::HTLCResolution { .. } => { handler.handle_event(bump); }, - } - } - Ok(()) - }, - ); - } - } - let mut had_new_txs = false; - for confirm_iter in 0..32 { - let mut found = false; - let mut pending_txs: Vec = Vec::new(); - for tx in broadcast_a.txn_broadcasted.borrow_mut().drain(..) { - pending_txs.push(tx); - } - for tx in broadcast_b.txn_broadcasted.borrow_mut().drain(..) { - pending_txs.push(tx); - } - for tx in broadcast_c.txn_broadcasted.borrow_mut().drain(..) { - pending_txs.push(tx); - } - pending_txs.sort_by_key(|tx| tx.lock_time.to_consensus_u32()); - let mut deferred_txs = pending_txs; - loop { - let mut next_deferred_txs = Vec::new(); - let mut progressed = false; - for tx in deferred_txs { - if confirm_tx_and_sync_wallets(&mut chain_state, tx.clone()) { - found = true; - progressed = true; - } else { - next_deferred_txs.push(tx); - } + } } - if !progressed { - // Keep only transactions that could become - // valid later, e.g. children waiting on their - // parent or timelocked claims waiting on height. - // Permanently stale conflicting spends should be - // dropped here or they create fake non-quiescence. - deferred_txs = next_deferred_txs - .into_iter() - .filter(|tx| should_retry_confirm_later(&chain_state, tx)) - .collect(); - break; + Ok(()) + }); + } + } + for confirm_iter in 0..32 { + let mut found = false; + let mut pending_txs: Vec = Vec::new(); + for tx in broadcast_a.txn_broadcasted.borrow_mut().drain(..) { + pending_txs.push(tx); + } + for tx in broadcast_b.txn_broadcasted.borrow_mut().drain(..) { + pending_txs.push(tx); + } + for tx in broadcast_c.txn_broadcasted.borrow_mut().drain(..) { + pending_txs.push(tx); + } + pending_txs.sort_by_key(|tx| tx.lock_time.to_consensus_u32()); + let mut deferred_txs = pending_txs; + loop { + let mut next_deferred_txs = Vec::new(); + let mut progressed = false; + for tx in deferred_txs { + if confirm_tx_and_sync_wallets(&mut chain_state, tx.clone()) { + found = true; + progressed = true; + } else { + next_deferred_txs.push(tx); } - deferred_txs = next_deferred_txs; - } - if !deferred_txs.is_empty() { - broadcast_a - .txn_broadcasted - .borrow_mut() - .extend(deferred_txs.into_iter()); } - if !found { + if !progressed { + // Keep only transactions that could become + // valid later, e.g. children waiting on their + // parent or timelocked claims waiting on height. + // Permanently stale conflicting spends should be + // dropped here or they create fake non-quiescence. + deferred_txs = next_deferred_txs + .into_iter() + .filter(|tx| should_retry_confirm_later(&chain_state, tx)) + .collect(); break; } - assert!( - confirm_iter < 31, - "flush_progress tx confirmation loop failed to quiesce: {}", - pending_work_summary!() - ); - had_new_txs = true; - sync_with_chain_state( - &chain_state, - &nodes[0], - &monitor_a, - &mut node_height_a, - None, - ); - sync_with_chain_state( - &chain_state, - &nodes[1], - &monitor_b, - &mut node_height_b, - None, - ); - sync_with_chain_state( - &chain_state, - &nodes[2], - &monitor_c, - &mut node_height_c, - None, - ); + deferred_txs = next_deferred_txs; + } + if !deferred_txs.is_empty() { + broadcast_a.txn_broadcasted.borrow_mut().extend(deferred_txs.into_iter()); } - if completed_monitor_update || had_new_txs || had_msg_or_ev { + if !found { + break; + } + assert!( + confirm_iter < 31, + "flush_progress tx confirmation loop failed to quiesce: {}", + pending_work_summary!() + ); + had_new_txs = true; + sync_with_chain_state( + &chain_state, + &nodes[0], + &monitor_a, + &mut node_height_a, + None, + ); + sync_with_chain_state( + &chain_state, + &nodes[1], + &monitor_b, + &mut node_height_b, + None, + ); + sync_with_chain_state( + &chain_state, + &nodes[2], + &monitor_c, + &mut node_height_c, + None, + ); + } + completed_monitor_update || had_new_txs || had_msg_or_ev + }}; + } + + macro_rules! flush_progress { + ($max_iters: expr) => {{ + let mut last_pass_no_updates = false; + for _ in 0..$max_iters { + if progress_round!() { last_pass_no_updates = false; continue; } @@ -2855,24 +2927,32 @@ pub fn do_test(data: &[u8], out: Out) { macro_rules! advance_chain_carefully { ($num_blocks: expr) => {{ - for _ in 0..$num_blocks { - // The force-close corpus used to jump 50/100/200 blocks in - // one shot, which made timeout paths dominate simply because - // queued messages, monitor completions, and claim propagation - // never got a chance to run. Advancing one block at a time - // with a bounded drain on both sides keeps those races - // possible without making them the default outcome. + if has_live_payment_work() { + unblock_all_signers!(); + // Hard mode forbids synthetic time jumps while a payment still + // needs receiver-side claim or sender-side completion. Final + // force-close cleanup advances under tighter control instead. flush_progress!(32); - if !has_pending_work!() { - // Stop early once there is nothing left that another - // empty block could unlock. Blindly consuming the entire - // requested jump mostly adds noise. - break; - } - chain_state.advance_height(1); - flush_progress!(32); - if !has_pending_work!() { - break; + } else { + for _ in 0..$num_blocks { + // The force-close corpus used to jump 50/100/200 blocks in + // one shot, which made timeout paths dominate simply because + // queued messages, monitor completions, and claim propagation + // never got a chance to run. Advancing one block at a time + // with a bounded drain on both sides keeps those races + // possible without making them the default outcome. + flush_progress!(32); + if !has_pending_work!() { + // Stop early once there is nothing left that another + // empty block could unlock. Blindly consuming the entire + // requested jump mostly adds noise. + break; + } + chain_state.advance_height(1); + flush_progress!(32); + if !has_pending_work!() { + break; + } } } }}; @@ -3389,6 +3469,11 @@ pub fn do_test(data: &[u8], out: Out) { // monitor updates, close events, and later HTLC claims. The settle // opcode below is what drives those consequences to quiescence. 0xd0 => { + // A close should act on the current pending state, not on stale queued + // messages or monitor completions from several opcodes ago. A bounded + // pre-close drain keeps those already-issued effects visible without + // also overriding signer-stall fuzzing via unblock_all_signers!. + flush_progress!(32); if nodes[0] .force_close_broadcasting_latest_txn( &chan_a_id, @@ -3398,9 +3483,15 @@ pub fn do_test(data: &[u8], out: Out) { .is_ok() { closed_channels.borrow_mut().insert(chan_a_id); + // Drain the immediate side effects of the close before the next + // fuzz opcode. Otherwise a fresh payment can still route across a + // channel that has just been force-closed but whose removal has not + // yet been observed by the rest of the harness. + flush_progress!(32); } }, 0xd1 => { + flush_progress!(32); if nodes[1] .force_close_broadcasting_latest_txn( &chan_b_id, @@ -3410,9 +3501,11 @@ pub fn do_test(data: &[u8], out: Out) { .is_ok() { closed_channels.borrow_mut().insert(chan_b_id); + flush_progress!(32); } }, 0xd2 => { + flush_progress!(32); if nodes[1] .force_close_broadcasting_latest_txn( &chan_a_id, @@ -3422,9 +3515,11 @@ pub fn do_test(data: &[u8], out: Out) { .is_ok() { closed_channels.borrow_mut().insert(chan_a_id); + flush_progress!(32); } }, 0xd3 => { + flush_progress!(32); if nodes[2] .force_close_broadcasting_latest_txn( &chan_b_id, @@ -3434,6 +3529,7 @@ pub fn do_test(data: &[u8], out: Out) { .is_ok() { closed_channels.borrow_mut().insert(chan_b_id); + flush_progress!(32); } }, @@ -3457,9 +3553,9 @@ pub fn do_test(data: &[u8], out: Out) { }, // Large synthetic height jumps. These exist so the corpus can - // reach CLTV and CSV-based force-close resolution states, but the - // helper above keeps them from becoming unrealistic "skip the - // network and time everything out" buttons. + // reach deeper force-close resolution states, but the helper above + // keeps them from becoming unrealistic "skip the network and time + // everything out" buttons. 0xdc => advance_chain_carefully!(50), 0xdd => advance_chain_carefully!(100), 0xde => advance_chain_carefully!(200), @@ -3820,63 +3916,83 @@ pub fn do_test(data: &[u8], out: Out) { } process_all_events!(); - let reconcile_pending_payments = || { - for node_idx in 0..nodes.len() { - let active_pending = nodes[node_idx] - .list_recent_payments() - .into_iter() - .filter_map(|payment| match payment { - RecentPaymentDetails::Pending { payment_id, .. } => { - Some(payment_id) - }, - _ => None, - }) - .collect::>(); - // Restarts rebuild outbound-payment state from the live - // ChannelManager. Drop any ids that remain only in the - // harness cache so the final invariant checks reflect the - // node state we are actually exercising. - pending_payments.borrow_mut()[node_idx] - .retain(|payment_id| active_pending.contains(payment_id)); - } - }; - // If any channels were force-closed, advance chain height until HTLC - // resolution activity quiesces. We keep this bounded to avoid hangs, - // but allow more rounds than the original fixed four-pass cleanup since - // RBF bumps and confirmation delays can legitimately require additional - // chain progress before a sender sees a terminal payment event. + // resolution activity quiesces and all tracked payment outcomes are + // observed. We advance only one block at a time, draining before and + // after each block so cleanup cannot manufacture timeout wins. + // + // Hard mode only forbids CLTV-expiry resolution for payments we + // explicitly claimed. Unclaimed HTLCs may still legitimately time + // out on-chain, so we only stop before an expiry boundary when doing + // so would be required to preserve a claimed payment's fulfillment. if !closed_channels.borrow().is_empty() { - let open_channels = nodes[0] - .list_channels() - .iter() - .chain(nodes[1].list_channels().iter()) - .chain(nodes[2].list_channels().iter()) - .cloned() - .collect::>(); - let open_refs: Vec<&_> = open_channels.iter().collect(); - for _ in 0..64 { - let requires_timelock_resolution = - [&monitor_a, &monitor_b, &monitor_c].iter().any(|monitor| { - has_timed_balance( - &monitor.chain_monitor.get_claimable_balances(&open_refs), - ) + for _ in 0..4096 { + flush_progress!(32); + for node in &nodes { + node.timer_tick_occurred(); + } + flush_progress!(32); + let open_channels = nodes[0] + .list_channels() + .iter() + .chain(nodes[1].list_channels().iter()) + .chain(nodes[2].list_channels().iter()) + .cloned() + .collect::>(); + let open_refs: Vec<&_> = open_channels.iter().collect(); + let balances_a = monitor_a.chain_monitor.get_claimable_balances(&open_refs); + let balances_b = monitor_b.chain_monitor.get_claimable_balances(&open_refs); + let balances_c = monitor_c.chain_monitor.get_claimable_balances(&open_refs); + let needs_payment_completion = has_live_payment_work(); + let has_cleanup_balances = !balances_a.is_empty() + || !balances_b.is_empty() || !balances_c + .is_empty(); + let can_drive_more_cleanup = has_cleanup_balances || has_pending_work!(); + let next_claimed_htlc_boundary = { + let claimed_hashes = claimed_payment_hashes.borrow(); + balances_a + .iter() + .chain(balances_b.iter()) + .chain(balances_c.iter()) + .filter_map(|balance| match balance { + Balance::ContentiousClaimable { + timeout_height, + payment_hash, + .. + } if claimed_hashes.contains(payment_hash) => Some(*timeout_height), + Balance::MaybeTimeoutClaimableHTLC { + claimable_height, + payment_hash, + .. + } if claimed_hashes.contains(payment_hash) => Some(*claimable_height), + Balance::MaybePreimageClaimableHTLC { + expiry_height, + payment_hash, + .. + } if claimed_hashes.contains(payment_hash) => Some(*expiry_height), + _ => None, + }) + .min() + }; + let can_advance_without_claimed_expiry = next_claimed_htlc_boundary + .map_or(true, |boundary| { + chain_state.tip_height().saturating_add(1) < boundary }); - reconcile_pending_payments(); - let has_pending_payments = - pending_payments.borrow().iter().any(|payments| !payments.is_empty()); - let requires_timelock_resolution = - requires_timelock_resolution || has_pending_payments; - if !requires_timelock_resolution { + if !needs_payment_completion { + break; + } + if !can_drive_more_cleanup { break; } - chain_state.advance_height(250); - process_all_events!(); + if has_unfinished_claims() && !can_advance_without_claimed_expiry { + break; + } + chain_state.advance_height(1); + flush_progress!(32); } } // Verify no payments are stuck - all should have resolved - reconcile_pending_payments(); for (idx, pending) in pending_payments.borrow().iter().enumerate() { assert!( pending.is_empty(), @@ -3888,17 +4004,25 @@ pub fn do_test(data: &[u8], out: Out) { ); } - // Verify that every payment claimed by a receiver resulted in a - // PaymentSent event at the sender. - let resolved = resolved_payments.borrow(); - for hash in claimed_payment_hashes.borrow().iter() { - let found = resolved.iter().any(|node_resolved| { - node_resolved.values().any(|h| h.as_ref() == Some(hash)) - }); + // Verify that every HTLC we explicitly claimed reached both terminal + // payment events, at the receiver and back at the sender. + let claimed_hashes = + claimed_payment_hashes.borrow().iter().copied().collect::>(); + for hash in claimed_hashes { + let receiver_saw_claim = + receiver_claimed_payment_hashes.borrow().contains(&hash); assert!( - found, - "Payment {:?} was claimed by receiver but sender never got PaymentSent", - hash + receiver_saw_claim, + "Payment {:?} was claimed with claim_funds but receiver never got PaymentClaimed: {}", + hash, + pending_work_summary!(), + ); + let sender_saw_sent = sender_sent_payment_hashes.borrow().contains(&hash); + assert!( + sender_saw_sent, + "Payment {:?} was claimed with claim_funds but sender never got PaymentSent: {}", + hash, + pending_work_summary!(), ); } @@ -3922,43 +4046,6 @@ pub fn do_test(data: &[u8], out: Out) { ); } - // After settlement, verify that closed channels have no - // ClaimableOnChannelClose balances (which would indicate the - // monitor still thinks the channel is open). - if !closed_channels.borrow().is_empty() { - let open_channels = nodes[0] - .list_channels() - .iter() - .chain(nodes[1].list_channels().iter()) - .chain(nodes[2].list_channels().iter()) - .map(|c| c.clone()) - .collect::>(); - let open_refs: Vec<&_> = open_channels.iter().collect(); - for (label, monitor) in - [("A", &monitor_a), ("B", &monitor_b), ("C", &monitor_c)] - { - let balances = monitor.chain_monitor.get_claimable_balances(&open_refs); - for balance in &balances { - if matches!(balance, Balance::ClaimableOnChannelClose { .. }) { - panic!( - "Monitor {} has ClaimableOnChannelClose balance after settlement: {:?}", - label, balance - ); - } - } - if !balances.is_empty() { - out.locked_write( - format!( - "Monitor {} has {} remaining balances after settlement.\n", - label, - balances.len() - ) - .as_bytes(), - ); - } - } - } - last_htlc_clear_fee_a = fee_est_a.ret_val.load(atomic::Ordering::Acquire); last_htlc_clear_fee_b = fee_est_b.ret_val.load(atomic::Ordering::Acquire); last_htlc_clear_fee_c = fee_est_c.ret_val.load(atomic::Ordering::Acquire); From d872ef888d195723868185fc168b20b109032838 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Fri, 17 Apr 2026 15:21:02 +0200 Subject: [PATCH 08/12] test_cases and docs [dropme] --- fc-crashes.md | 162 ++++++++++++++++++ fuzz/FC-INFO.md | 88 ++++++++++ ...h-02830a6ff7757f3570924b0c0fd9118a7cdd9770 | Bin 0 -> 24 bytes ...h-0473b0e767d9a98de62538ce5afcbbc2e6ec5af2 | 1 + ...h-05e175d40f60b823f730fa874d98dc10dd2bb6ad | 1 + ...h-07bdc4e56ee67bd2ffa409f76529199d748ab2d8 | 1 + ...h-096cc3008264dccaefb945f5a4b7a2d3c9f8e90c | 1 + ...h-09a17e06913dea74dba796940cec86cb4e2dd597 | 1 + ...h-09f5a41270b07f70a031884cbdfd081e8600923e | Bin 0 -> 22 bytes ...h-0b87d8b430697fe9d1781a38f41a68ebcf7b18c1 | 1 + ...h-0c3334736f5c55e44088d6140580354827026732 | 1 + ...h-0f0ca42c8b4c815495919663652db18483d5e846 | 1 + ...h-15b45517356c182051c2b334e09c00f4f9368e94 | 1 + ...h-18062bd37528e06c4921e7ef7df2b2c3e676823b | 1 + ...h-22125d8a200205d52723ec232f5aab710856f4b0 | Bin 0 -> 22 bytes ...h-228ea00412a2fab1e866fc6df32ffd00bbfe81ad | Bin 0 -> 24 bytes ...h-242de208110143401fcf4e1ebaa7d9d38fb93611 | 1 + ...h-24f1373b1cf51f95af854d6d8730336b77728007 | 1 + ...h-2923c14608fb259c21862cd71ffeb6ac74b0ba32 | 1 + ...h-2a0852bec1d75334538dacec26831db6995b6e33 | 1 + ...h-2d93541536e19c030d95d236e6be545352d98b80 | 1 + ...h-2e002fcfdc76c5981f5f93c0f842b548fb56c7a7 | 1 + ...h-2fad50c7fd20b250f0349887445af198124900df | Bin 0 -> 23 bytes ...h-2fcd63b2ed709dfcd9c6a08dc673d1f896b6cdad | Bin 0 -> 26 bytes ...h-315119ea09b9febec156d212fe57020def4b5af4 | Bin 0 -> 25 bytes ...h-32a013d8bd38f3ba39d4a214ba0780edd41ccb85 | Bin 0 -> 23 bytes ...h-33c08a8f15f1c842df5da4fc92228d00606573f9 | 1 + ...h-33e77c2f720493e306bbfea79f151388ca7a04ea | Bin 0 -> 24 bytes ...h-37a18356d608c97415c0a1bef6a0f13fe04c8b97 | Bin 0 -> 22 bytes ...h-380ee6f8c1030828f4d80582154b0418fca58c90 | Bin 0 -> 20 bytes ...h-38192a6cb0500969f301c7a6742949ecd213bfae | 1 + ...h-387c18b4c7235aa1960400de5b0d5798202ec3b1 | 1 + ...h-3bb94b7b4397397caa5eb0e9ba6abb9a18028270 | Bin 0 -> 23 bytes ...h-3be4d9d7a75c8459b3ec349474c7fc206b00fe9c | Bin 0 -> 26 bytes ...h-3cda5b606ce05f4207207e8fd1480fe530a51b13 | 1 + ...h-3f8a6e5b806235b795ebea3d6998943a3ab6ff9d | Bin 0 -> 22 bytes ...h-41ffe016736ddfef0eb1d877b35a0c85bd5cfd5f | 1 + ...h-45240f379a3a24948c4b091fd658a9f0ef4d4963 | 1 + ...h-49e1240588c1b4507b24c4f07dae75faef02a639 | 1 + ...h-4da789d875488d8f244bccefaff4295ae801c745 | Bin 0 -> 21 bytes ...h-4e4b47b5a0f4c4689868a3003ae7d62e5ac78484 | 1 + ...h-53d6404dc8dee21adf112f3c909459f67e176301 | Bin 0 -> 24 bytes ...h-544eff2c026e0464aff1a9afaa4acd2912e93267 | 1 + ...h-54a3422e8e1c578813d5cfce1f8b732040fc668e | 1 + ...h-55fd3e4e7c2506a9ce067b0e0a468161db22dec0 | Bin 0 -> 20 bytes ...h-56271abf5206dd39ac1a1035d49d41f61ee0606e | 1 + ...h-5be7542ec7a98b835a2c3dca63e3d89a76050fe6 | 3 + ...h-5d2ca379ca5dabcbfae13c3eca104e48a4bf94c9 | 1 + ...h-63164e99d1a0561c352ea11be619b8505a83ceb4 | Bin 0 -> 20 bytes ...h-6aec66d5104839013b44f977a01915c29f2e6795 | 1 + ...h-6af2409d5c331f44f76e165e735cd2e9104aed9e | 1 + ...h-6b5c5549ee7ed6e7fcf9613d62c295fd65d100ce | 1 + ...h-6bd8c4ea12175b25bb1d239699622ba5485248cf | 1 + ...h-6bda1f46384cf85ae2d9ca8048619963a9416ddc | 1 + ...h-767cf8ac05cf878f93f55fe21f96a9e76b28c5f9 | 1 + ...h-7776698efb54442fa8170cb39b7c7bf72e515335 | 1 + ...h-79790f24a47ad8f39398df48800b946cd85fc3fe | 1 + ...h-7ab7fa1fb4303a91c57ec241fefdf5826d2b52aa | Bin 0 -> 26 bytes ...h-7b7826cea32794a2ab2c245cd3dc024355b07c78 | Bin 0 -> 16 bytes ...h-7c72226eeba2eb5192d9b7adfead405d3b93fdf9 | Bin 0 -> 24 bytes ...h-7cb0cf9df154821deb68a78001ce9c0e27f97b0a | 1 + ...h-815718bf6e59d981220f037f7509c9cfe5401485 | 1 + ...h-8453a4a3cf9dd9f60e5aa40fdce440b69f62869d | 1 + ...h-86ee8ae4c13784d3d750f6d4b970ec0852ea2bc3 | 1 + ...h-87093ec5446a84482f5a728fc65a51a15b6de843 | Bin 0 -> 27 bytes ...h-87f98b753291bd37f92795d32e2df4c3597dd6dd | Bin 0 -> 25 bytes ...h-8ab54f3642a60a239a7bb787838f3e5a6b6f4f41 | 1 + ...h-8ec6798103af6cedfdec68373991c0c0a73e3770 | 1 + ...h-8f5cc4f6de42f52dcb571b6c0f21df957eb25462 | Bin 0 -> 27 bytes ...h-8fb6d213ac7d14f6c62c09e7baf392f01e8688d0 | 1 + ...h-90c560825e852e3dfb64e09d6764b85cf9f7689d | 1 + ...h-91d8898837e425d607ef36ed73fa364b0fa58121 | Bin 0 -> 17 bytes ...h-91ebb8583ed7705e2601334e52428ea5eb80a681 | Bin 0 -> 23 bytes ...h-93c44c96a5c5e1d4532370b2c77bb372170bd59b | 1 + ...h-9c69d63a708c0a83d2d1fa60577a1a9270924ff2 | 1 + ...h-9c84f405725b7c171338f776b7ac7f3a3b010f34 | Bin 0 -> 17 bytes ...h-a235e98ab95f66315cef361c49eea5483ce2d91a | 1 + ...h-a6628891c34498ca2cb4122c2ee66fe4ba6cd01d | Bin 0 -> 26 bytes ...h-a8f59ca92bcc53e042fd759493c67a35f308721a | 1 + ...h-ace48b23767637be15eb3763e88170f7aab17cd4 | Bin 0 -> 23 bytes ...h-adf5f907d4bc584e6348b7188532f6fc08cda464 | 1 + ...h-af7499de68300f3346be7b69ff913c8da2394d23 | 1 + ...h-b2e70396bda55d716c022a683df49d72e28b5cae | Bin 0 -> 24 bytes ...h-ba5dd0ee55c764b2ae71543e95fd63c496d924bd | 1 + ...h-baa2cd71d1e22c966f3c2ddc44cf5b297da5d671 | 1 + ...h-c1fe932fa21c4382ba71ec745790386f010b939c | 1 + ...h-c29e58a510e698fc8205e4896a938adb92424105 | 1 + ...h-c7b166535d5d3591604aeb239b01592f24fff27b | 1 + ...h-cb39e58d20b35ceb4ecd9fc8dd91272e308f11a1 | 1 + ...h-cc144c9fa2f889e3c3665b1e7c870ddd41cb3e15 | 1 + ...h-cec678efd9c2c03dccf92f62c20e9520566d130f | Bin 0 -> 25 bytes ...h-cedac69cfff63a360470d6f051164b149f74bc18 | Bin 0 -> 25 bytes ...h-cf44c3acf507cae6fd00e0bf331d18536c551ce1 | 1 + ...h-d09e9319d459f21b180f1c730fbf4e89840bd6c5 | Bin 0 -> 20 bytes ...h-d11e5e5259e57e32f120f0d005bc52aead73d099 | 1 + ...h-d3ee0bad80fbd14f1f62903fc6d23f26ed5eb405 | 1 + ...h-d5124444b5e39d9a67c395e6325d340fff97a159 | 1 + ...h-d87da6cc047b35d69808787157394a0ac7c9ff92 | Bin 0 -> 26 bytes ...h-d91352ebdfa46f3734403e7e041bf0faa559e97a | 1 + ...h-d9affe3db851b50c3b1186ff86f97710cfd115b0 | 1 + ...h-db2606af8c9a718bd0da6a6e03c51fd4c84909cd | Bin 0 -> 17 bytes ...h-dbf141642a66403570204baf8a310783885e081d | 1 + ...h-dd67d75d834201769b29d89a5243fdae7f6d8ad1 | 1 + ...h-df426fe2abe15519a7ad994034bd2711f26f80af | 1 + ...h-ea07f1a57bd66e8a0b48347a45f12a4e48fa4b02 | Bin 0 -> 25 bytes ...h-efc04dc2a68b17479ad445cce2b84a91a7d3e9b9 | 1 + ...h-f4567ec41df8f30f9c0975e2b9cb3bed9278df8c | Bin 0 -> 26 bytes ...h-f804080d84b3bfc7adfe563ad1ac9013733983f6 | Bin 0 -> 24 bytes ...h-f995b58793f0e17361d409df7ddb99d7c14873cd | 1 + ...h-fd80c35839107ef932a09d1fd63e34d2a6cd6451 | 1 + ...h-fda69e901e92ce81134859dfbd53ceec84393aeb | 1 + .../fc_advance_before_drain | Bin 0 -> 9 bytes .../fc_advance_before_drain_keyed_anchors | 1 + ..._advance_before_drain_zero_fee_commitments | 1 + .../fc_after_claim_before_forward | Bin 0 -> 18 bytes ...c_after_claim_before_forward_keyed_anchors | 1 + ..._claim_before_forward_zero_fee_commitments | 1 + .../chanmon_consistency/fc_after_disconnect | Bin 0 -> 10 bytes .../fc_after_disconnect_keyed_anchors | 1 + .../fc_after_disconnect_zero_fee_commitments | 1 + .../chanmon_consistency/fc_after_fee_update | Bin 0 -> 11 bytes .../fc_after_fee_update_keyed_anchors | 1 + .../fc_after_fee_update_zero_fee_commitments | 1 + .../chanmon_consistency/fc_after_timer_ticks | Bin 0 -> 13 bytes .../fc_after_timer_ticks_keyed_anchors | 1 + .../fc_after_timer_ticks_zero_fee_commitments | 1 + .../chanmon_consistency/fc_all_channels | Bin 0 -> 12 bytes .../fc_all_channels_keyed_anchors | 1 + .../fc_all_channels_zero_fee_commitments | 1 + .../fc_async_complete_after | 2 + .../fc_async_complete_after_keyed_anchors | 2 + ..._async_complete_after_zero_fee_commitments | 2 + .../fc_async_hop_middle_closes | 4 + .../fc_async_hop_middle_closes_keyed_anchors | 4 + ...ync_hop_middle_closes_zero_fee_commitments | 4 + .../chanmon_consistency/fc_async_many_pays | 2 + .../fc_async_many_pays_keyed_anchors | 2 + .../fc_async_many_pays_zero_fee_commitments | 2 + .../chanmon_consistency/fc_async_no_complete | 1 + .../fc_async_no_complete_keyed_anchors | 1 + .../fc_async_no_complete_zero_fee_commitments | 1 + .../fc_async_pending_never_complete | 1 + ...async_pending_never_complete_keyed_anchors | 1 + ...ending_never_complete_zero_fee_commitments | 1 + .../chanmon_consistency/fc_async_restart | 1 + .../fc_async_restart_keyed_anchors | 1 + .../fc_async_restart_zero_fee_commitments | 1 + .../chanmon_consistency/fc_b_closes_both | Bin 0 -> 10 bytes .../fc_b_closes_both_hop_inflight | Bin 0 -> 17 bytes ...c_b_closes_both_hop_inflight_keyed_anchors | 1 + ...ses_both_hop_inflight_zero_fee_commitments | 1 + .../fc_b_closes_both_keyed_anchors | 1 + .../fc_b_closes_both_zero_fee_commitments | 1 + .../fc_bc_during_hop_ab_only | Bin 0 -> 12 bytes .../fc_bc_during_hop_ab_only_keyed_anchors | 1 + ...bc_during_hop_ab_only_zero_fee_commitments | 1 + .../chanmon_consistency/fc_bc_while_ab_htlc | Bin 0 -> 15 bytes .../fc_bc_while_ab_htlc_keyed_anchors | 1 + .../fc_bc_while_ab_htlc_zero_fee_commitments | 1 + .../chanmon_consistency/fc_bidir_htlcs | Bin 0 -> 15 bytes .../fc_bidir_htlcs_keyed_anchors | 1 + .../fc_bidir_htlcs_zero_fee_commitments | 1 + .../fc_both_sides_same_chan | Bin 0 -> 10 bytes .../fc_both_sides_same_chan_keyed_anchors | 1 + ..._both_sides_same_chan_zero_fee_commitments | 1 + .../fc_c_initiates_b_restart | Bin 0 -> 10 bytes .../fc_c_initiates_b_restart_keyed_anchors | 1 + ...c_initiates_b_restart_zero_fee_commitments | 1 + .../chanmon_consistency/fc_cascade_c_then_b | Bin 0 -> 14 bytes .../fc_cascade_c_then_b_keyed_anchors | 1 + .../fc_cascade_c_then_b_zero_fee_commitments | 1 + .../fc_claimable_on_close_needs_confirmation | 1 + .../fc_claimed_payment_sender_completion | 1 + .../fc_close_then_disconnect_all | Bin 0 -> 12 bytes ...fc_close_then_disconnect_all_keyed_anchors | 1 + ...e_then_disconnect_all_zero_fee_commitments | 1 + .../chanmon_consistency/fc_direct_pay_claimed | Bin 0 -> 16 bytes .../fc_direct_pay_claimed_keyed_anchors | 1 + ...fc_direct_pay_claimed_zero_fee_commitments | 1 + .../chanmon_consistency/fc_disabled_signers | Bin 0 -> 13 bytes .../fc_disabled_signers_keyed_anchors | 1 + .../fc_disabled_signers_zero_fee_commitments | 1 + .../fc_disconnect_close_reconnect | Bin 0 -> 13 bytes ...c_disconnect_close_reconnect_keyed_anchors | 1 + ...nnect_close_reconnect_zero_fee_commitments | 1 + .../fc_disconnect_drain_reconnect | Bin 0 -> 13 bytes ...c_disconnect_drain_reconnect_keyed_anchors | 1 + ...nnect_drain_reconnect_zero_fee_commitments | 1 + .../chanmon_consistency/fc_drain_a_only | Bin 0 -> 7 bytes .../fc_drain_a_only_keyed_anchors | 1 + .../fc_drain_a_only_zero_fee_commitments | 1 + .../chanmon_consistency/fc_during_reconnect | Bin 0 -> 13 bytes .../fc_during_reconnect_keyed_anchors | 1 + .../fc_during_reconnect_zero_fee_commitments | 1 + .../chanmon_consistency/fc_dust_htlcs | Bin 0 -> 19 bytes .../fc_dust_htlcs_keyed_anchors | 1 + .../fc_dust_htlcs_zero_fee_commitments | 1 + .../fc_events_between_drains | Bin 0 -> 15 bytes .../fc_events_between_drains_keyed_anchors | 1 + ...events_between_drains_zero_fee_commitments | 1 + .../chanmon_consistency/fc_events_only | Bin 0 -> 12 bytes .../fc_events_only_keyed_anchors | 1 + .../fc_events_only_zero_fee_commitments | 1 + .../chanmon_consistency/fc_exact_cltv_height | Bin 0 -> 15 bytes .../fc_exact_cltv_height_keyed_anchors | 1 + .../fc_exact_cltv_height_zero_fee_commitments | 1 + .../chanmon_consistency/fc_hop_b_has_preimage | Bin 0 -> 20 bytes .../fc_hop_b_has_preimage_keyed_anchors | 1 + ...fc_hop_b_has_preimage_zero_fee_commitments | 1 + .../fc_hop_before_bc_commit | Bin 0 -> 13 bytes .../fc_hop_before_bc_commit_keyed_anchors | 1 + ..._hop_before_bc_commit_zero_fee_commitments | 1 + .../chanmon_consistency/fc_hop_mid_flight | Bin 0 -> 16 bytes .../fc_hop_mid_flight_keyed_anchors | 1 + .../fc_hop_mid_flight_zero_fee_commitments | 1 + .../chanmon_consistency/fc_htlc_late_signer | Bin 0 -> 17 bytes .../fc_htlc_late_signer_keyed_anchors | 1 + .../fc_htlc_late_signer_zero_fee_commitments | 1 + .../chanmon_consistency/fc_immediate_settle | Bin 0 -> 3 bytes .../fc_immediate_settle_keyed_anchors | 1 + .../fc_immediate_settle_zero_fee_commitments | 1 + .../fc_inprogress_monitors | Bin 0 -> 11 bytes .../fc_inprogress_monitors_keyed_anchors | Bin 0 -> 11 bytes ...c_inprogress_monitors_zero_fee_commitments | Bin 0 -> 11 bytes .../fc_interleaved_channels | Bin 0 -> 17 bytes .../fc_interleaved_channels_keyed_anchors | 1 + ..._interleaved_channels_zero_fee_commitments | 1 + .../fc_large_payment_resolve | Bin 0 -> 16 bytes .../fc_large_payment_resolve_keyed_anchors | 1 + ...large_payment_resolve_zero_fee_commitments | 1 + .../chanmon_consistency/fc_many_htlcs | Bin 0 -> 15 bytes .../fc_many_htlcs_keyed_anchors | 1 + .../fc_many_htlcs_zero_fee_commitments | 1 + .../fc_mid_fulfill_propagation | Bin 0 -> 21 bytes .../fc_mid_fulfill_propagation_keyed_anchors | 1 + ...d_fulfill_propagation_zero_fee_commitments | 1 + .../chanmon_consistency/fc_msgs_before_drain | Bin 0 -> 13 bytes .../fc_msgs_before_drain_keyed_anchors | 1 + .../fc_msgs_before_drain_zero_fee_commitments | 1 + .../chanmon_consistency/fc_multi_drain_rounds | Bin 0 -> 16 bytes .../fc_multi_drain_rounds_keyed_anchors | 1 + ...fc_multi_drain_rounds_zero_fee_commitments | 1 + .../chanmon_consistency/fc_no_settle | Bin 0 -> 3 bytes .../fc_no_settle_keyed_anchors | 1 + .../fc_no_settle_zero_fee_commitments | 1 + .../chanmon_consistency/fc_node_restart | Bin 0 -> 10 bytes .../fc_node_restart_keyed_anchors | 1 + .../fc_node_restart_zero_fee_commitments | 1 + .../chanmon_consistency/fc_one_msg_at_a_time | Bin 0 -> 13 bytes .../fc_one_msg_at_a_time_keyed_anchors | 1 + .../fc_one_msg_at_a_time_zero_fee_commitments | 1 + .../fc_pay_claim_close_pay | Bin 0 -> 17 bytes .../fc_pay_claim_close_pay_keyed_anchors | 1 + ...c_pay_claim_close_pay_zero_fee_commitments | 1 + .../chanmon_consistency/fc_pending_monitor | Bin 0 -> 15 bytes .../fc_pending_monitor_keyed_anchors | Bin 0 -> 15 bytes .../fc_pending_monitor_zero_fee_commitments | Bin 0 -> 15 bytes .../chanmon_consistency/fc_rapid_fire | Bin 0 -> 12 bytes .../fc_rapid_fire_keyed_anchors | 1 + .../fc_rapid_fire_zero_fee_commitments | 1 + .../chanmon_consistency/fc_reconnect | Bin 0 -> 13 bytes .../fc_reconnect_broadcast_announcements | 1 + .../fc_reconnect_keyed_anchors | 1 + .../fc_reconnect_zero_fee_commitments | 1 + .../fc_repeated_same_channel | Bin 0 -> 11 bytes .../fc_repeated_same_channel_keyed_anchors | 1 + ...repeated_same_channel_zero_fee_commitments | 1 + .../fc_restart_mid_resolve | Bin 0 -> 10 bytes .../fc_restart_mid_resolve_keyed_anchors | 1 + ...c_restart_mid_resolve_zero_fee_commitments | 1 + .../fc_restart_then_counterparty_closes | Bin 0 -> 10 bytes ...art_then_counterparty_closes_keyed_anchors | 1 + ...n_counterparty_closes_zero_fee_commitments | 1 + .../chanmon_consistency/fc_reverse_hop | Bin 0 -> 16 bytes .../fc_reverse_hop_keyed_anchors | 1 + .../fc_reverse_hop_zero_fee_commitments | 1 + .../fc_signer_disabled_holder | Bin 0 -> 14 bytes .../fc_signer_disabled_holder_keyed_anchors | 1 + ...igner_disabled_holder_zero_fee_commitments | 1 + .../fc_stale_monitor_restart | Bin 0 -> 13 bytes .../fc_stale_monitor_restart_keyed_anchors | 1 + ...stale_monitor_restart_zero_fee_commitments | 1 + .../chanmon_consistency/fc_sync_one_block | Bin 0 -> 14 bytes .../fc_sync_one_block_keyed_anchors | 1 + .../fc_sync_one_block_zero_fee_commitments | 1 + .../chanmon_consistency/fc_sync_to_tip | Bin 0 -> 7 bytes .../fc_sync_to_tip_keyed_anchors | 1 + .../fc_sync_to_tip_zero_fee_commitments | 1 + .../chanmon_consistency/fc_then_send | Bin 0 -> 10 bytes .../fc_then_send_keyed_anchors | 1 + .../fc_then_send_zero_fee_commitments | 1 + .../chanmon_consistency/fc_timer_tick_after | Bin 0 -> 15 bytes .../fc_timer_tick_after_keyed_anchors | 1 + .../fc_timer_tick_after_zero_fee_commitments | 1 + .../fc_unclaimed_mpp_timeout_variant_a | 1 + .../fc_unclaimed_mpp_timeout_variant_b | 1 + .../chanmon_consistency/force_close_basic | Bin 0 -> 9 bytes .../force_close_basic_async | 1 + .../force_close_basic_async_keyed_anchors | 1 + ...rce_close_basic_async_zero_fee_commitments | 1 + .../force_close_basic_keyed_anchors | 1 + .../force_close_basic_zero_fee_commitments | 1 + .../force_close_both_directions | Bin 0 -> 10 bytes .../force_close_both_directions_async | 1 + ..._close_both_directions_async_keyed_anchors | 1 + ...both_directions_async_zero_fee_commitments | 1 + .../force_close_both_directions_keyed_anchors | 1 + ...close_both_directions_zero_fee_commitments | 1 + .../force_close_htlc_needs_height | Bin 0 -> 11 bytes .../force_close_htlc_needs_height_async | 1 + ...lose_htlc_needs_height_async_keyed_anchors | 1 + ...lc_needs_height_async_zero_fee_commitments | 1 + ...orce_close_htlc_needs_height_keyed_anchors | 1 + ...ose_htlc_needs_height_zero_fee_commitments | 1 + .../force_close_htlc_resolved | Bin 0 -> 11 bytes .../force_close_htlc_resolved_async | 1 + ...ce_close_htlc_resolved_async_keyed_anchors | 1 + ...e_htlc_resolved_async_zero_fee_commitments | 1 + .../force_close_htlc_resolved_keyed_anchors | 1 + ...e_close_htlc_resolved_zero_fee_commitments | 1 + .../force_close_middle_node | Bin 0 -> 9 bytes .../force_close_middle_node_async | 1 + ...orce_close_middle_node_async_keyed_anchors | 1 + ...ose_middle_node_async_zero_fee_commitments | 1 + .../force_close_middle_node_keyed_anchors | 1 + ...rce_close_middle_node_zero_fee_commitments | 1 + .../force_close_no_confirm | Bin 0 -> 2 bytes .../force_close_no_confirm_async | 1 + ...force_close_no_confirm_async_keyed_anchors | 1 + ...lose_no_confirm_async_zero_fee_commitments | 1 + .../force_close_no_confirm_keyed_anchors | 1 + ...orce_close_no_confirm_zero_fee_commitments | 1 + .../force_close_three_node_preimage | Bin 0 -> 14 bytes .../force_close_three_node_preimage_async | 4 + ...se_three_node_preimage_async_keyed_anchors | 4 + ...e_node_preimage_async_zero_fee_commitments | 4 + ...ce_close_three_node_preimage_keyed_anchors | 1 + ...e_three_node_preimage_zero_fee_commitments | 1 + .../ldk_crash_channelmanager_19484 | 1 + .../ldk_crash_channelmanager_9836 | Bin 0 -> 13 bytes .../ldk_crash_channelmonitor_2727 | 1 + .../ldk_crash_onchaintx_1025 | Bin 0 -> 14 bytes .../ldk_crash_onchaintx_913 | 1 + .../chanmon_consistency/ldk_crash_signer_395 | 1 + ...t-0103befb3dc5aa050668752668d04e85bd1fc14e | Bin 0 -> 24 bytes ...t-05fc1bb98f2a3b29e826a4de636474de0b23c895 | Bin 0 -> 25 bytes ...t-2bd72986b31d87f9260cf627e63971b5b8310a60 | Bin 0 -> 22 bytes ...t-3d9c399d0e2d915375da243fb57023df803a5dc6 | 1 + ...t-475eb92f6d72aed80ce7cdaed4181b99b11b2fcd | Bin 0 -> 26 bytes ...t-4c5cc7debdfdf2569e21b13b21c4270a9b558267 | Bin 0 -> 20 bytes ...t-505b331015cbe51169de31e09acc6d8330c8e385 | 2 + ...t-593eb3357e98be529b0ef35f21577ef6eede171b | Bin 0 -> 18 bytes ...t-6ee72dbba68dbca58038d2f9b8525e4d0df25f94 | 1 + ...t-76a82aa89161d0428192e725650324a74a710dca | 1 + ...t-7a7ea04ead9439ad7db2eefb23f6e242d547e459 | Bin 0 -> 25 bytes ...t-8250da1564cda1a1dde38a431859afab8ac2934d | Bin 0 -> 23 bytes ...t-838b7d436a92ae2a68aa9ad9badd88cbf96b407b | Bin 0 -> 24 bytes ...t-87b55c5b37383fe43420089fd3e8ccecbb034b44 | 1 + ...t-885f446335ae279baed408d42af8c398dfdb8c9b | 1 + ...t-8a81e4c066465a2975ef22625c0b91da6332a2c8 | Bin 0 -> 24 bytes ...t-8f2aebf3aeeb70d8edd39a886e30beb770f3b42b | Bin 0 -> 23 bytes ...t-91d97d9eea2bd59f746681ad822488262e832ff1 | Bin 0 -> 26 bytes ...t-95a90908391d3398084b77eb11ff5c9d7fdde008 | Bin 0 -> 24 bytes ...t-a31cdfc423211489c841a6ddd067f9e6cf5bed4b | Bin 0 -> 21 bytes ...t-b1c4840ea1279dd8d6080d79373ae55bbcad3061 | Bin 0 -> 25 bytes ...t-b6ef84eec94d70bbc385c98c4ab0bac77da00a2f | 1 + ...t-bae8693182b102dfebab143a0f48992dad76245d | Bin 0 -> 23 bytes ...t-bcab049322729e275e3bbdacebc633495da7643f | Bin 0 -> 26 bytes ...t-d5afdff02a253c9f2fbce95cbaf730eb210128fa | 1 + ...t-d6494f068fb2b2d31f1ac8627752692b3c8b7d2f | 1 + ...t-edd3f8168217501dd93f3c24d09c2c095cdf7784 | Bin 0 -> 14 bytes ...t-fcbcc131184e33d5b000820b0972f6197b0801d2 | 1 + 372 files changed, 524 insertions(+) create mode 100644 fc-crashes.md create mode 100644 fuzz/FC-INFO.md create mode 100644 fuzz/test_cases/chanmon_consistency/crash-02830a6ff7757f3570924b0c0fd9118a7cdd9770 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-0473b0e767d9a98de62538ce5afcbbc2e6ec5af2 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-05e175d40f60b823f730fa874d98dc10dd2bb6ad create mode 100644 fuzz/test_cases/chanmon_consistency/crash-07bdc4e56ee67bd2ffa409f76529199d748ab2d8 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-096cc3008264dccaefb945f5a4b7a2d3c9f8e90c create mode 100644 fuzz/test_cases/chanmon_consistency/crash-09a17e06913dea74dba796940cec86cb4e2dd597 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-09f5a41270b07f70a031884cbdfd081e8600923e create mode 100644 fuzz/test_cases/chanmon_consistency/crash-0b87d8b430697fe9d1781a38f41a68ebcf7b18c1 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-0c3334736f5c55e44088d6140580354827026732 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-0f0ca42c8b4c815495919663652db18483d5e846 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-15b45517356c182051c2b334e09c00f4f9368e94 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-18062bd37528e06c4921e7ef7df2b2c3e676823b create mode 100644 fuzz/test_cases/chanmon_consistency/crash-22125d8a200205d52723ec232f5aab710856f4b0 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-228ea00412a2fab1e866fc6df32ffd00bbfe81ad create mode 100644 fuzz/test_cases/chanmon_consistency/crash-242de208110143401fcf4e1ebaa7d9d38fb93611 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-24f1373b1cf51f95af854d6d8730336b77728007 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-2923c14608fb259c21862cd71ffeb6ac74b0ba32 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-2a0852bec1d75334538dacec26831db6995b6e33 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-2d93541536e19c030d95d236e6be545352d98b80 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-2e002fcfdc76c5981f5f93c0f842b548fb56c7a7 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-2fad50c7fd20b250f0349887445af198124900df create mode 100644 fuzz/test_cases/chanmon_consistency/crash-2fcd63b2ed709dfcd9c6a08dc673d1f896b6cdad create mode 100644 fuzz/test_cases/chanmon_consistency/crash-315119ea09b9febec156d212fe57020def4b5af4 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-32a013d8bd38f3ba39d4a214ba0780edd41ccb85 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-33c08a8f15f1c842df5da4fc92228d00606573f9 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-33e77c2f720493e306bbfea79f151388ca7a04ea create mode 100644 fuzz/test_cases/chanmon_consistency/crash-37a18356d608c97415c0a1bef6a0f13fe04c8b97 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-380ee6f8c1030828f4d80582154b0418fca58c90 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-38192a6cb0500969f301c7a6742949ecd213bfae create mode 100644 fuzz/test_cases/chanmon_consistency/crash-387c18b4c7235aa1960400de5b0d5798202ec3b1 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-3bb94b7b4397397caa5eb0e9ba6abb9a18028270 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-3be4d9d7a75c8459b3ec349474c7fc206b00fe9c create mode 100644 fuzz/test_cases/chanmon_consistency/crash-3cda5b606ce05f4207207e8fd1480fe530a51b13 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-3f8a6e5b806235b795ebea3d6998943a3ab6ff9d create mode 100644 fuzz/test_cases/chanmon_consistency/crash-41ffe016736ddfef0eb1d877b35a0c85bd5cfd5f create mode 100644 fuzz/test_cases/chanmon_consistency/crash-45240f379a3a24948c4b091fd658a9f0ef4d4963 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-49e1240588c1b4507b24c4f07dae75faef02a639 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-4da789d875488d8f244bccefaff4295ae801c745 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-4e4b47b5a0f4c4689868a3003ae7d62e5ac78484 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-53d6404dc8dee21adf112f3c909459f67e176301 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-544eff2c026e0464aff1a9afaa4acd2912e93267 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-54a3422e8e1c578813d5cfce1f8b732040fc668e create mode 100644 fuzz/test_cases/chanmon_consistency/crash-55fd3e4e7c2506a9ce067b0e0a468161db22dec0 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-56271abf5206dd39ac1a1035d49d41f61ee0606e create mode 100644 fuzz/test_cases/chanmon_consistency/crash-5be7542ec7a98b835a2c3dca63e3d89a76050fe6 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-5d2ca379ca5dabcbfae13c3eca104e48a4bf94c9 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-63164e99d1a0561c352ea11be619b8505a83ceb4 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-6aec66d5104839013b44f977a01915c29f2e6795 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-6af2409d5c331f44f76e165e735cd2e9104aed9e create mode 100644 fuzz/test_cases/chanmon_consistency/crash-6b5c5549ee7ed6e7fcf9613d62c295fd65d100ce create mode 100644 fuzz/test_cases/chanmon_consistency/crash-6bd8c4ea12175b25bb1d239699622ba5485248cf create mode 100644 fuzz/test_cases/chanmon_consistency/crash-6bda1f46384cf85ae2d9ca8048619963a9416ddc create mode 100644 fuzz/test_cases/chanmon_consistency/crash-767cf8ac05cf878f93f55fe21f96a9e76b28c5f9 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-7776698efb54442fa8170cb39b7c7bf72e515335 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-79790f24a47ad8f39398df48800b946cd85fc3fe create mode 100644 fuzz/test_cases/chanmon_consistency/crash-7ab7fa1fb4303a91c57ec241fefdf5826d2b52aa create mode 100644 fuzz/test_cases/chanmon_consistency/crash-7b7826cea32794a2ab2c245cd3dc024355b07c78 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-7c72226eeba2eb5192d9b7adfead405d3b93fdf9 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-7cb0cf9df154821deb68a78001ce9c0e27f97b0a create mode 100644 fuzz/test_cases/chanmon_consistency/crash-815718bf6e59d981220f037f7509c9cfe5401485 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-8453a4a3cf9dd9f60e5aa40fdce440b69f62869d create mode 100644 fuzz/test_cases/chanmon_consistency/crash-86ee8ae4c13784d3d750f6d4b970ec0852ea2bc3 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-87093ec5446a84482f5a728fc65a51a15b6de843 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-87f98b753291bd37f92795d32e2df4c3597dd6dd create mode 100644 fuzz/test_cases/chanmon_consistency/crash-8ab54f3642a60a239a7bb787838f3e5a6b6f4f41 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-8ec6798103af6cedfdec68373991c0c0a73e3770 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-8f5cc4f6de42f52dcb571b6c0f21df957eb25462 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-8fb6d213ac7d14f6c62c09e7baf392f01e8688d0 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-90c560825e852e3dfb64e09d6764b85cf9f7689d create mode 100644 fuzz/test_cases/chanmon_consistency/crash-91d8898837e425d607ef36ed73fa364b0fa58121 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-91ebb8583ed7705e2601334e52428ea5eb80a681 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-93c44c96a5c5e1d4532370b2c77bb372170bd59b create mode 100644 fuzz/test_cases/chanmon_consistency/crash-9c69d63a708c0a83d2d1fa60577a1a9270924ff2 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-9c84f405725b7c171338f776b7ac7f3a3b010f34 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-a235e98ab95f66315cef361c49eea5483ce2d91a create mode 100644 fuzz/test_cases/chanmon_consistency/crash-a6628891c34498ca2cb4122c2ee66fe4ba6cd01d create mode 100644 fuzz/test_cases/chanmon_consistency/crash-a8f59ca92bcc53e042fd759493c67a35f308721a create mode 100644 fuzz/test_cases/chanmon_consistency/crash-ace48b23767637be15eb3763e88170f7aab17cd4 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-adf5f907d4bc584e6348b7188532f6fc08cda464 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-af7499de68300f3346be7b69ff913c8da2394d23 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-b2e70396bda55d716c022a683df49d72e28b5cae create mode 100644 fuzz/test_cases/chanmon_consistency/crash-ba5dd0ee55c764b2ae71543e95fd63c496d924bd create mode 100644 fuzz/test_cases/chanmon_consistency/crash-baa2cd71d1e22c966f3c2ddc44cf5b297da5d671 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-c1fe932fa21c4382ba71ec745790386f010b939c create mode 100644 fuzz/test_cases/chanmon_consistency/crash-c29e58a510e698fc8205e4896a938adb92424105 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-c7b166535d5d3591604aeb239b01592f24fff27b create mode 100644 fuzz/test_cases/chanmon_consistency/crash-cb39e58d20b35ceb4ecd9fc8dd91272e308f11a1 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-cc144c9fa2f889e3c3665b1e7c870ddd41cb3e15 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-cec678efd9c2c03dccf92f62c20e9520566d130f create mode 100644 fuzz/test_cases/chanmon_consistency/crash-cedac69cfff63a360470d6f051164b149f74bc18 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-cf44c3acf507cae6fd00e0bf331d18536c551ce1 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-d09e9319d459f21b180f1c730fbf4e89840bd6c5 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-d11e5e5259e57e32f120f0d005bc52aead73d099 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-d3ee0bad80fbd14f1f62903fc6d23f26ed5eb405 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-d5124444b5e39d9a67c395e6325d340fff97a159 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-d87da6cc047b35d69808787157394a0ac7c9ff92 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-d91352ebdfa46f3734403e7e041bf0faa559e97a create mode 100644 fuzz/test_cases/chanmon_consistency/crash-d9affe3db851b50c3b1186ff86f97710cfd115b0 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-db2606af8c9a718bd0da6a6e03c51fd4c84909cd create mode 100644 fuzz/test_cases/chanmon_consistency/crash-dbf141642a66403570204baf8a310783885e081d create mode 100644 fuzz/test_cases/chanmon_consistency/crash-dd67d75d834201769b29d89a5243fdae7f6d8ad1 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-df426fe2abe15519a7ad994034bd2711f26f80af create mode 100644 fuzz/test_cases/chanmon_consistency/crash-ea07f1a57bd66e8a0b48347a45f12a4e48fa4b02 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-efc04dc2a68b17479ad445cce2b84a91a7d3e9b9 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-f4567ec41df8f30f9c0975e2b9cb3bed9278df8c create mode 100644 fuzz/test_cases/chanmon_consistency/crash-f804080d84b3bfc7adfe563ad1ac9013733983f6 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-f995b58793f0e17361d409df7ddb99d7c14873cd create mode 100644 fuzz/test_cases/chanmon_consistency/crash-fd80c35839107ef932a09d1fd63e34d2a6cd6451 create mode 100644 fuzz/test_cases/chanmon_consistency/crash-fda69e901e92ce81134859dfbd53ceec84393aeb create mode 100644 fuzz/test_cases/chanmon_consistency/fc_advance_before_drain create mode 100644 fuzz/test_cases/chanmon_consistency/fc_advance_before_drain_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_advance_before_drain_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_after_claim_before_forward create mode 100644 fuzz/test_cases/chanmon_consistency/fc_after_claim_before_forward_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_after_claim_before_forward_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_after_disconnect create mode 100644 fuzz/test_cases/chanmon_consistency/fc_after_disconnect_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_after_disconnect_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_after_fee_update create mode 100644 fuzz/test_cases/chanmon_consistency/fc_after_fee_update_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_after_fee_update_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_after_timer_ticks create mode 100644 fuzz/test_cases/chanmon_consistency/fc_after_timer_ticks_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_after_timer_ticks_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_all_channels create mode 100644 fuzz/test_cases/chanmon_consistency/fc_all_channels_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_all_channels_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_async_complete_after create mode 100644 fuzz/test_cases/chanmon_consistency/fc_async_complete_after_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_async_complete_after_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_async_hop_middle_closes create mode 100644 fuzz/test_cases/chanmon_consistency/fc_async_hop_middle_closes_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_async_hop_middle_closes_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_async_many_pays create mode 100644 fuzz/test_cases/chanmon_consistency/fc_async_many_pays_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_async_many_pays_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_async_no_complete create mode 100644 fuzz/test_cases/chanmon_consistency/fc_async_no_complete_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_async_no_complete_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_async_pending_never_complete create mode 100644 fuzz/test_cases/chanmon_consistency/fc_async_pending_never_complete_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_async_pending_never_complete_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_async_restart create mode 100644 fuzz/test_cases/chanmon_consistency/fc_async_restart_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_async_restart_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_b_closes_both create mode 100644 fuzz/test_cases/chanmon_consistency/fc_b_closes_both_hop_inflight create mode 100644 fuzz/test_cases/chanmon_consistency/fc_b_closes_both_hop_inflight_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_b_closes_both_hop_inflight_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_b_closes_both_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_b_closes_both_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_bc_during_hop_ab_only create mode 100644 fuzz/test_cases/chanmon_consistency/fc_bc_during_hop_ab_only_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_bc_during_hop_ab_only_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_bc_while_ab_htlc create mode 100644 fuzz/test_cases/chanmon_consistency/fc_bc_while_ab_htlc_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_bc_while_ab_htlc_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_bidir_htlcs create mode 100644 fuzz/test_cases/chanmon_consistency/fc_bidir_htlcs_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_bidir_htlcs_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_both_sides_same_chan create mode 100644 fuzz/test_cases/chanmon_consistency/fc_both_sides_same_chan_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_both_sides_same_chan_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_c_initiates_b_restart create mode 100644 fuzz/test_cases/chanmon_consistency/fc_c_initiates_b_restart_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_c_initiates_b_restart_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_cascade_c_then_b create mode 100644 fuzz/test_cases/chanmon_consistency/fc_cascade_c_then_b_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_cascade_c_then_b_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_claimable_on_close_needs_confirmation create mode 100644 fuzz/test_cases/chanmon_consistency/fc_claimed_payment_sender_completion create mode 100644 fuzz/test_cases/chanmon_consistency/fc_close_then_disconnect_all create mode 100644 fuzz/test_cases/chanmon_consistency/fc_close_then_disconnect_all_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_close_then_disconnect_all_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_direct_pay_claimed create mode 100644 fuzz/test_cases/chanmon_consistency/fc_direct_pay_claimed_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_direct_pay_claimed_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_disabled_signers create mode 100644 fuzz/test_cases/chanmon_consistency/fc_disabled_signers_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_disabled_signers_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_disconnect_close_reconnect create mode 100644 fuzz/test_cases/chanmon_consistency/fc_disconnect_close_reconnect_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_disconnect_close_reconnect_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_disconnect_drain_reconnect create mode 100644 fuzz/test_cases/chanmon_consistency/fc_disconnect_drain_reconnect_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_disconnect_drain_reconnect_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_drain_a_only create mode 100644 fuzz/test_cases/chanmon_consistency/fc_drain_a_only_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_drain_a_only_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_during_reconnect create mode 100644 fuzz/test_cases/chanmon_consistency/fc_during_reconnect_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_during_reconnect_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_dust_htlcs create mode 100644 fuzz/test_cases/chanmon_consistency/fc_dust_htlcs_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_dust_htlcs_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_events_between_drains create mode 100644 fuzz/test_cases/chanmon_consistency/fc_events_between_drains_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_events_between_drains_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_events_only create mode 100644 fuzz/test_cases/chanmon_consistency/fc_events_only_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_events_only_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_exact_cltv_height create mode 100644 fuzz/test_cases/chanmon_consistency/fc_exact_cltv_height_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_exact_cltv_height_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_hop_b_has_preimage create mode 100644 fuzz/test_cases/chanmon_consistency/fc_hop_b_has_preimage_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_hop_b_has_preimage_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_hop_before_bc_commit create mode 100644 fuzz/test_cases/chanmon_consistency/fc_hop_before_bc_commit_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_hop_before_bc_commit_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_hop_mid_flight create mode 100644 fuzz/test_cases/chanmon_consistency/fc_hop_mid_flight_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_hop_mid_flight_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_htlc_late_signer create mode 100644 fuzz/test_cases/chanmon_consistency/fc_htlc_late_signer_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_htlc_late_signer_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_immediate_settle create mode 100644 fuzz/test_cases/chanmon_consistency/fc_immediate_settle_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_immediate_settle_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_inprogress_monitors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_inprogress_monitors_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_inprogress_monitors_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_interleaved_channels create mode 100644 fuzz/test_cases/chanmon_consistency/fc_interleaved_channels_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_interleaved_channels_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_large_payment_resolve create mode 100644 fuzz/test_cases/chanmon_consistency/fc_large_payment_resolve_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_large_payment_resolve_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_many_htlcs create mode 100644 fuzz/test_cases/chanmon_consistency/fc_many_htlcs_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_many_htlcs_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_mid_fulfill_propagation create mode 100644 fuzz/test_cases/chanmon_consistency/fc_mid_fulfill_propagation_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_mid_fulfill_propagation_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_msgs_before_drain create mode 100644 fuzz/test_cases/chanmon_consistency/fc_msgs_before_drain_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_msgs_before_drain_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_multi_drain_rounds create mode 100644 fuzz/test_cases/chanmon_consistency/fc_multi_drain_rounds_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_multi_drain_rounds_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_no_settle create mode 100644 fuzz/test_cases/chanmon_consistency/fc_no_settle_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_no_settle_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_node_restart create mode 100644 fuzz/test_cases/chanmon_consistency/fc_node_restart_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_node_restart_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_one_msg_at_a_time create mode 100644 fuzz/test_cases/chanmon_consistency/fc_one_msg_at_a_time_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_one_msg_at_a_time_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_pay_claim_close_pay create mode 100644 fuzz/test_cases/chanmon_consistency/fc_pay_claim_close_pay_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_pay_claim_close_pay_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_pending_monitor create mode 100644 fuzz/test_cases/chanmon_consistency/fc_pending_monitor_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_pending_monitor_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_rapid_fire create mode 100644 fuzz/test_cases/chanmon_consistency/fc_rapid_fire_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_rapid_fire_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_reconnect create mode 100644 fuzz/test_cases/chanmon_consistency/fc_reconnect_broadcast_announcements create mode 100644 fuzz/test_cases/chanmon_consistency/fc_reconnect_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_reconnect_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_repeated_same_channel create mode 100644 fuzz/test_cases/chanmon_consistency/fc_repeated_same_channel_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_repeated_same_channel_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_restart_mid_resolve create mode 100644 fuzz/test_cases/chanmon_consistency/fc_restart_mid_resolve_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_restart_mid_resolve_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_restart_then_counterparty_closes create mode 100644 fuzz/test_cases/chanmon_consistency/fc_restart_then_counterparty_closes_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_restart_then_counterparty_closes_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_reverse_hop create mode 100644 fuzz/test_cases/chanmon_consistency/fc_reverse_hop_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_reverse_hop_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_signer_disabled_holder create mode 100644 fuzz/test_cases/chanmon_consistency/fc_signer_disabled_holder_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_signer_disabled_holder_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_stale_monitor_restart create mode 100644 fuzz/test_cases/chanmon_consistency/fc_stale_monitor_restart_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_stale_monitor_restart_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_sync_one_block create mode 100644 fuzz/test_cases/chanmon_consistency/fc_sync_one_block_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_sync_one_block_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_sync_to_tip create mode 100644 fuzz/test_cases/chanmon_consistency/fc_sync_to_tip_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_sync_to_tip_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_then_send create mode 100644 fuzz/test_cases/chanmon_consistency/fc_then_send_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_then_send_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_timer_tick_after create mode 100644 fuzz/test_cases/chanmon_consistency/fc_timer_tick_after_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/fc_timer_tick_after_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/fc_unclaimed_mpp_timeout_variant_a create mode 100644 fuzz/test_cases/chanmon_consistency/fc_unclaimed_mpp_timeout_variant_b create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_basic create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_basic_async create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_basic_async_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_basic_async_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_basic_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_basic_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_both_directions create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_both_directions_async create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_both_directions_async_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_both_directions_async_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_both_directions_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_both_directions_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_htlc_needs_height create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_htlc_needs_height_async create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_htlc_needs_height_async_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_htlc_needs_height_async_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_htlc_needs_height_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_htlc_needs_height_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_htlc_resolved create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_htlc_resolved_async create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_htlc_resolved_async_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_htlc_resolved_async_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_htlc_resolved_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_htlc_resolved_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_middle_node create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_middle_node_async create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_middle_node_async_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_middle_node_async_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_middle_node_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_middle_node_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_no_confirm create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_no_confirm_async create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_no_confirm_async_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_no_confirm_async_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_no_confirm_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_no_confirm_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_three_node_preimage create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_three_node_preimage_async create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_three_node_preimage_async_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_three_node_preimage_async_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_three_node_preimage_keyed_anchors create mode 100644 fuzz/test_cases/chanmon_consistency/force_close_three_node_preimage_zero_fee_commitments create mode 100644 fuzz/test_cases/chanmon_consistency/ldk_crash_channelmanager_19484 create mode 100644 fuzz/test_cases/chanmon_consistency/ldk_crash_channelmanager_9836 create mode 100644 fuzz/test_cases/chanmon_consistency/ldk_crash_channelmonitor_2727 create mode 100644 fuzz/test_cases/chanmon_consistency/ldk_crash_onchaintx_1025 create mode 100644 fuzz/test_cases/chanmon_consistency/ldk_crash_onchaintx_913 create mode 100644 fuzz/test_cases/chanmon_consistency/ldk_crash_signer_395 create mode 100644 fuzz/test_cases/chanmon_consistency/timeout-0103befb3dc5aa050668752668d04e85bd1fc14e create mode 100644 fuzz/test_cases/chanmon_consistency/timeout-05fc1bb98f2a3b29e826a4de636474de0b23c895 create mode 100644 fuzz/test_cases/chanmon_consistency/timeout-2bd72986b31d87f9260cf627e63971b5b8310a60 create mode 100644 fuzz/test_cases/chanmon_consistency/timeout-3d9c399d0e2d915375da243fb57023df803a5dc6 create mode 100644 fuzz/test_cases/chanmon_consistency/timeout-475eb92f6d72aed80ce7cdaed4181b99b11b2fcd create mode 100644 fuzz/test_cases/chanmon_consistency/timeout-4c5cc7debdfdf2569e21b13b21c4270a9b558267 create mode 100644 fuzz/test_cases/chanmon_consistency/timeout-505b331015cbe51169de31e09acc6d8330c8e385 create mode 100644 fuzz/test_cases/chanmon_consistency/timeout-593eb3357e98be529b0ef35f21577ef6eede171b create mode 100644 fuzz/test_cases/chanmon_consistency/timeout-6ee72dbba68dbca58038d2f9b8525e4d0df25f94 create mode 100644 fuzz/test_cases/chanmon_consistency/timeout-76a82aa89161d0428192e725650324a74a710dca create mode 100644 fuzz/test_cases/chanmon_consistency/timeout-7a7ea04ead9439ad7db2eefb23f6e242d547e459 create mode 100644 fuzz/test_cases/chanmon_consistency/timeout-8250da1564cda1a1dde38a431859afab8ac2934d create mode 100644 fuzz/test_cases/chanmon_consistency/timeout-838b7d436a92ae2a68aa9ad9badd88cbf96b407b create mode 100644 fuzz/test_cases/chanmon_consistency/timeout-87b55c5b37383fe43420089fd3e8ccecbb034b44 create mode 100644 fuzz/test_cases/chanmon_consistency/timeout-885f446335ae279baed408d42af8c398dfdb8c9b create mode 100644 fuzz/test_cases/chanmon_consistency/timeout-8a81e4c066465a2975ef22625c0b91da6332a2c8 create mode 100644 fuzz/test_cases/chanmon_consistency/timeout-8f2aebf3aeeb70d8edd39a886e30beb770f3b42b create mode 100644 fuzz/test_cases/chanmon_consistency/timeout-91d97d9eea2bd59f746681ad822488262e832ff1 create mode 100644 fuzz/test_cases/chanmon_consistency/timeout-95a90908391d3398084b77eb11ff5c9d7fdde008 create mode 100644 fuzz/test_cases/chanmon_consistency/timeout-a31cdfc423211489c841a6ddd067f9e6cf5bed4b create mode 100644 fuzz/test_cases/chanmon_consistency/timeout-b1c4840ea1279dd8d6080d79373ae55bbcad3061 create mode 100644 fuzz/test_cases/chanmon_consistency/timeout-b6ef84eec94d70bbc385c98c4ab0bac77da00a2f create mode 100644 fuzz/test_cases/chanmon_consistency/timeout-bae8693182b102dfebab143a0f48992dad76245d create mode 100644 fuzz/test_cases/chanmon_consistency/timeout-bcab049322729e275e3bbdacebc633495da7643f create mode 100644 fuzz/test_cases/chanmon_consistency/timeout-d5afdff02a253c9f2fbce95cbaf730eb210128fa create mode 100644 fuzz/test_cases/chanmon_consistency/timeout-d6494f068fb2b2d31f1ac8627752692b3c8b7d2f create mode 100644 fuzz/test_cases/chanmon_consistency/timeout-edd3f8168217501dd93f3c24d09c2c095cdf7784 create mode 100644 fuzz/test_cases/chanmon_consistency/timeout-fcbcc131184e33d5b000820b0972f6197b0801d2 diff --git a/fc-crashes.md b/fc-crashes.md new file mode 100644 index 00000000000..a257e7966ba --- /dev/null +++ b/fc-crashes.md @@ -0,0 +1,162 @@ +# Force-close fuzzer LDK crashes + +Minimized crash sequences found by the chanmon_consistency fuzzer with +force-close support. All crashes are `debug_assert` or `panic!` inside +LDK, not in the fuzzer harness. Byte 0 encodes monitor styles (bits +0-2) and channel type (bits 3-4: 0=Legacy, 1=KeyedAnchors). + +## 1. channelmonitor.rs:2727 - HTLC input not found in transaction + +``` +debug_assert!(htlc_input_idx_opt.is_some()); +``` + +When resolving an HTLC spend, the monitor searches for the HTLC +outpoint in the spending transaction's inputs but doesn't find it. +Falls back to index 0 in release mode, which would produce incorrect +tracking. + +Minimized (17 bytes): +``` +0x40 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xdc 0xde 0xff +``` + +Byte 0 = 0x40: Legacy channels, no async monitors. The sequence is +mostly 0xff (settlement) repeated, with height advances (0xdc, 0xde) +near the end. This suggests the crash happens during settlement when +processing on-chain HTLC spends after repeated settlement attempts. + +## 2. onchaintx.rs:913 - Duplicate claim ID in pending requests + +``` +debug_assert!(self.pending_claim_requests.get(&claim_id).is_none()); +``` + +The OnchainTxHandler registers a claim event with a claim_id that +already exists in the pending_claim_requests map. + +Minimized (10 bytes): +``` +0x08 0xd2 0x70 0x70 0x71 0x70 0x10 0x19 0xde 0xff +``` + +Byte 0 = 0x08: KeyedAnchors channels, no async monitors. +- 0xd2: B force-closes the A-B channel +- 0x70/0x71: disconnect/reconnect peers +- 0x10, 0x19: process messages on nodes A and B +- 0xde: advance chain 200 blocks +- 0xff: settle + +B force-closes, peers disconnect and reconnect, messages are exchanged, +then height advances and settlement triggers the duplicate claim. + +## 3. onchaintx.rs:1025 - Inconsistent internal maps + +``` +panic!("Inconsistencies between pending_claim_requests map and claimable_outpoints map"); +``` + +The OnchainTxHandler detects that its `pending_claim_requests` and +`claimable_outpoints` maps are out of sync. + +Minimized (14 bytes): +``` +0x00 0x3c 0x11 0x19 0xd0 0xde 0xff 0xff 0x19 0x21 0x19 0xde 0x26 0xff +``` + +Byte 0 = 0x00: Legacy channels, all monitors completed. +- 0x3c: send hop payment A->B->C (1M msat) +- 0x11, 0x19: process messages to commit HTLC on A-B +- 0xd0: A force-closes A-B +- 0xde: advance 200 blocks +- 0xff: settle (first round) +- 0xff: settle again (second round, processes more messages) +- 0x19, 0x21, 0x19: continue processing B and C messages +- 0xde: advance 200 more blocks +- 0x26: process events on node C +- 0xff: settle (third round) + +A hop payment partially committed, then A force-closes. Multiple +settlement rounds with continued message processing in between triggers +the internal map inconsistency. + +## 4. test_channel_signer.rs:395 - Signing revoked commitment + +``` +panic!("can only sign the next two unrevoked commitment numbers, revoked={} vs requested={}") +``` + +The test channel signer is asked to sign an HTLC transaction for a +commitment number that has already been revoked. + +Minimized (18 bytes): +``` +0x22 0x71 0x71 0x71 0x71 0x71 0x71 0x71 0xff 0xff 0xff 0xff 0xff 0xff 0xde 0xde 0xb5 0xff +``` + +Byte 0 = 0x22: Legacy channels, async monitors on node B. +- 0x71: disconnect B-C peers (repeated, only first effective) +- 0xff: settle (repeated 6 times) +- 0xde 0xde: advance 400 blocks +- 0xb5: restart node B with alternate monitor state +- 0xff: settle + +Async monitors on B with peer disconnection, repeated settlements, +height advances, and a node restart with a different monitor state. +The stale monitor combined with the restart puts B's signer in a state +where it's asked to sign for an already-revoked commitment. + +## 5. channelmanager.rs:9836 - Payment blocker not found + +``` +debug_assert!(found_blocker); +``` + +During payment processing, the ChannelManager expects to find a +specific blocker entry for an in-flight payment but it's missing. + +Minimized (13 bytes): +``` +0x00 0x3c 0x11 0x19 0x11 0x1f 0x19 0x21 0x19 0x27 0x27 0xde 0xff +``` + +Byte 0 = 0x00: Legacy channels, all monitors completed. +- 0x3c: send hop A->B->C (1M msat) +- 0x11, 0x19, 0x11: commit HTLC on A-B +- 0x1f: B processes events (forwards HTLC to C) +- 0x19, 0x21, 0x19: commit HTLC on B-C +- 0x27, 0x27: C processes events (claims payment) +- 0xde: advance 200 blocks +- 0xff: settle + +A straightforward A->B->C hop payment that completes normally (C +claims), followed by a height advance and settlement. No force-close +in this sequence, so the height advance before settlement may cause +HTLC timeout processing that conflicts with the claim path. + +## 6. channelmanager.rs:19484 - Monitor update ID ordering violation + +``` +debug_assert!(update.update_id >= pending_update.update_id); +``` + +A ChannelMonitorUpdate has an update_id that is less than a pending +update's id, violating the expected monotonic ordering. + +Minimized (10 bytes): +``` +0x84 0x70 0x11 0x19 0x11 0x1f 0xd0 0x11 0x1f 0xba +``` + +Byte 0 = 0x84: Legacy channels, no async monitors, high bits set +(bits 3-4 = 0, bits 7 and 2 set). +- 0x70: disconnect A-B peers +- 0x11, 0x19, 0x11: process messages (likely reestablish after setup) +- 0x1f: process B events +- 0xd0: A force-closes A-B channel +- 0x11: process A messages +- 0x1f: process B events +- 0xba: restart node B with alternate monitor state + +A force-close followed by continued message/event processing and a +node B restart triggers a monitor update with an out-of-order ID. diff --git a/fuzz/FC-INFO.md b/fuzz/FC-INFO.md new file mode 100644 index 00000000000..1c8983603e7 --- /dev/null +++ b/fuzz/FC-INFO.md @@ -0,0 +1,88 @@ +# Force-Close Fuzzing Notes + +This file records the current contract for `chanmon_consistency` force-close +coverage. It is intentionally short. Keep branch history and one-off debugging +notes elsewhere. + +## Goal + +Force-close fuzzing here should: + +- exercise realistic off-chain to on-chain transitions +- keep force-close from changing the eventual outcome of claimed payments +- allow unclaimed HTLCs to resolve by CLTV timeout +- drive the harness far enough that it observes real terminal outcomes +- avoid manufacturing timeout wins by starving message delivery or claim + propagation + +## Hard-Mode Invariant + +The current hard mode is: + +- once the harness calls `claim_funds`, that HTLC must eventually produce + `PaymentClaimed` at the receiver +- every such claimed HTLC must also eventually produce `PaymentSent` at the + sender +- going on-chain does not create an exception to those two requirements +- unclaimed HTLCs may still fail by CLTV expiry +- CSV waits on force-close outputs are normal and expected; they are not + payment outcome changes +- a payment disappearing from `list_recent_payments()` is not enough, the + harness must observe or drive the terminal outcome directly + +In this mode, the following are harness failures: + +- `HTLCHandlingFailed::Receive` after we already chose to claim the HTLC +- a receiver-side claim without a later sender-side `PaymentSent` +- a claimed HTLC resolving by CLTV timeout instead of fulfillment +- cleanup stopping while live balances or other pending work still show that + more progress is possible + +## Timeouts + +Do not conflate CSV and CLTV: + +- CSV is normal force-close settlement latency +- CLTV expiry changes the HTLC outcome + +The harness should keep driving through CSV waits. It should only protect +claimed HTLCs from CLTV-expiry resolution. + +## Harness Rules + +The main rules for preserving the invariant are: + +- advance large height jumps one block at a time, with bounded draining before + and after each block +- process queued messages and events before confirming newly broadcast + transactions, so preimages can propagate before timeout paths win +- keep sender-side payment bookkeeping independent of + `list_recent_payments()` +- keep driving while `ClaimableOnChannelClose`, HTLC-related claimable balances, + queued messages, pending monitor updates, or pending broadcasts still show + unresolved work +- only stop before a CLTV boundary when crossing it would let a claimed HTLC + expire instead of fulfill +- do not hide pending-payment state behind unrelated auto-driving before an + explicit force-close opcode; a bounded pre-close drain is acceptable when it + is only making already-queued work visible + +## Review Checklist + +When changing this harness, verify: + +- claimed HTLCs still require both `PaymentClaimed` and `PaymentSent` +- unclaimed HTLCs may still time out on-chain +- force-close opcodes still act on the currently pending state +- large synthetic height jumps do not become blind timeout buttons again +- sender-side obligations are not reconciled away through local caches + +## Verification + +The standard check is: + +```bash +~/repo/rl-tools/run_fuzz_runner.sh --timeout-secs 20 +``` + +Re-run the full corpus after any meaningful force-close harness change. diff --git a/fuzz/test_cases/chanmon_consistency/crash-02830a6ff7757f3570924b0c0fd9118a7cdd9770 b/fuzz/test_cases/chanmon_consistency/crash-02830a6ff7757f3570924b0c0fd9118a7cdd9770 new file mode 100644 index 0000000000000000000000000000000000000000..57c626b8597071187a3bc4e90ce05655fd65b429 GIT binary patch literal 24 dcmZQL!C@mPDJUzcD0%V5&0F{G0l7ed{{UQa2#Npz literal 0 HcmV?d00001 diff --git a/fuzz/test_cases/chanmon_consistency/crash-0473b0e767d9a98de62538ce5afcbbc2e6ec5af2 b/fuzz/test_cases/chanmon_consistency/crash-0473b0e767d9a98de62538ce5afcbbc2e6ec5af2 new file mode 100644 index 00000000000..ba413134fbb --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/crash-0473b0e767d9a98de62538ce5afcbbc2e6ec5af2 @@ -0,0 +1 @@ +pppppp0ppp0 \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/crash-05e175d40f60b823f730fa874d98dc10dd2bb6ad b/fuzz/test_cases/chanmon_consistency/crash-05e175d40f60b823f730fa874d98dc10dd2bb6ad new file mode 100644 index 00000000000..cabed892750 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/crash-05e175d40f60b823f730fa874d98dc10dd2bb6ad @@ -0,0 +1 @@ +lls \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/crash-07bdc4e56ee67bd2ffa409f76529199d748ab2d8 b/fuzz/test_cases/chanmon_consistency/crash-07bdc4e56ee67bd2ffa409f76529199d748ab2d8 new file mode 100644 index 00000000000..eb3ac3716d2 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/crash-07bdc4e56ee67bd2ffa409f76529199d748ab2d8 @@ -0,0 +1 @@ +pppppp0% \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/crash-096cc3008264dccaefb945f5a4b7a2d3c9f8e90c b/fuzz/test_cases/chanmon_consistency/crash-096cc3008264dccaefb945f5a4b7a2d3c9f8e90c new file mode 100644 index 00000000000..f00662619a1 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/crash-096cc3008264dccaefb945f5a4b7a2d3c9f8e90c @@ -0,0 +1 @@ +<!''إ!޺Ѻ \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/crash-09a17e06913dea74dba796940cec86cb4e2dd597 b/fuzz/test_cases/chanmon_consistency/crash-09a17e06913dea74dba796940cec86cb4e2dd597 new file mode 100644 index 00000000000..30543451915 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/crash-09a17e06913dea74dba796940cec86cb4e2dd597 @@ -0,0 +1 @@ +<! \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/crash-09f5a41270b07f70a031884cbdfd081e8600923e b/fuzz/test_cases/chanmon_consistency/crash-09f5a41270b07f70a031884cbdfd081e8600923e new file mode 100644 index 0000000000000000000000000000000000000000..e0ff1832a4fa6f420610b625b63dfc2b521be628 GIT binary patch literal 22 ecmdi1 literal 0 HcmV?d00001 diff --git a/fuzz/test_cases/chanmon_consistency/crash-228ea00412a2fab1e866fc6df32ffd00bbfe81ad b/fuzz/test_cases/chanmon_consistency/crash-228ea00412a2fab1e866fc6df32ffd00bbfe81ad new file mode 100644 index 0000000000000000000000000000000000000000..4a6a76ade6c4327ec5b327290a1d158a6a649f45 GIT binary patch literal 24 fcmd-u6y(3j00f4D|Nr0PziD&p9>>4`|Na92UV95K literal 0 HcmV?d00001 diff --git a/fuzz/test_cases/chanmon_consistency/crash-242de208110143401fcf4e1ebaa7d9d38fb93611 b/fuzz/test_cases/chanmon_consistency/crash-242de208110143401fcf4e1ebaa7d9d38fb93611 new file mode 100644 index 00000000000..76da0f6debb --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/crash-242de208110143401fcf4e1ebaa7d9d38fb93611 @@ -0,0 +1 @@ +*Ҹ \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/crash-24f1373b1cf51f95af854d6d8730336b77728007 b/fuzz/test_cases/chanmon_consistency/crash-24f1373b1cf51f95af854d6d8730336b77728007 new file mode 100644 index 00000000000..0064fa17f19 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/crash-24f1373b1cf51f95af854d6d8730336b77728007 @@ -0,0 +1 @@ +*tA2 \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/crash-2923c14608fb259c21862cd71ffeb6ac74b0ba32 b/fuzz/test_cases/chanmon_consistency/crash-2923c14608fb259c21862cd71ffeb6ac74b0ba32 new file mode 100644 index 00000000000..ff1549ef79f --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/crash-2923c14608fb259c21862cd71ffeb6ac74b0ba32 @@ -0,0 +1 @@ +p0p0ذZ \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/crash-2a0852bec1d75334538dacec26831db6995b6e33 b/fuzz/test_cases/chanmon_consistency/crash-2a0852bec1d75334538dacec26831db6995b6e33 new file mode 100644 index 00000000000..f5e273ff51f --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/crash-2a0852bec1d75334538dacec26831db6995b6e33 @@ -0,0 +1 @@ +p0b \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/crash-2d93541536e19c030d95d236e6be545352d98b80 b/fuzz/test_cases/chanmon_consistency/crash-2d93541536e19c030d95d236e6be545352d98b80 new file mode 100644 index 00000000000..0c7432d7c20 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/crash-2d93541536e19c030d95d236e6be545352d98b80 @@ -0,0 +1 @@ +*tA \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/crash-2e002fcfdc76c5981f5f93c0f842b548fb56c7a7 b/fuzz/test_cases/chanmon_consistency/crash-2e002fcfdc76c5981f5f93c0f842b548fb56c7a7 new file mode 100644 index 00000000000..bd5c0aab70a --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/crash-2e002fcfdc76c5981f5f93c0f842b548fb56c7a7 @@ -0,0 +1 @@ +p0t0Z \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/crash-2fad50c7fd20b250f0349887445af198124900df b/fuzz/test_cases/chanmon_consistency/crash-2fad50c7fd20b250f0349887445af198124900df new file mode 100644 index 0000000000000000000000000000000000000000..44d0be6fc50646d858865e0158f9e4641b1e387d GIT binary patch literal 23 ecmd{{cHF2-W}q literal 0 HcmV?d00001 diff --git a/fuzz/test_cases/chanmon_consistency/crash-33c08a8f15f1c842df5da4fc92228d00606573f9 b/fuzz/test_cases/chanmon_consistency/crash-33c08a8f15f1c842df5da4fc92228d00606573f9 new file mode 100644 index 00000000000..391b9204000 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/crash-33c08a8f15f1c842df5da4fc92228d00606573f9 @@ -0,0 +1 @@ +<0sslqlqq \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/crash-33e77c2f720493e306bbfea79f151388ca7a04ea b/fuzz/test_cases/chanmon_consistency/crash-33e77c2f720493e306bbfea79f151388ca7a04ea new file mode 100644 index 0000000000000000000000000000000000000000..2c4a1c6cac69675d19dd6a25823cf46a586de0c6 GIT binary patch literal 24 dcmd-w;QIf+VM*$eB|z%lz5hFbVDtaY3;?vR50(G` literal 0 HcmV?d00001 diff --git a/fuzz/test_cases/chanmon_consistency/crash-37a18356d608c97415c0a1bef6a0f13fe04c8b97 b/fuzz/test_cases/chanmon_consistency/crash-37a18356d608c97415c0a1bef6a0f13fe04c8b97 new file mode 100644 index 0000000000000000000000000000000000000000..877a41dd6ae570a9dedab0719c6337030f68c2c7 GIT binary patch literal 22 dcmd07LEu=>Px# literal 0 HcmV?d00001 diff --git a/fuzz/test_cases/chanmon_consistency/crash-8ab54f3642a60a239a7bb787838f3e5a6b6f4f41 b/fuzz/test_cases/chanmon_consistency/crash-8ab54f3642a60a239a7bb787838f3e5a6b6f4f41 new file mode 100644 index 00000000000..50f706994ac --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/crash-8ab54f3642a60a239a7bb787838f3e5a6b6f4f41 @@ -0,0 +1 @@ +  \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/crash-8ec6798103af6cedfdec68373991c0c0a73e3770 b/fuzz/test_cases/chanmon_consistency/crash-8ec6798103af6cedfdec68373991c0c0a73e3770 new file mode 100644 index 00000000000..877960d1655 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/crash-8ec6798103af6cedfdec68373991c0c0a73e3770 @@ -0,0 +1 @@ +<:!'' \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/crash-8f5cc4f6de42f52dcb571b6c0f21df957eb25462 b/fuzz/test_cases/chanmon_consistency/crash-8f5cc4f6de42f52dcb571b6c0f21df957eb25462 new file mode 100644 index 0000000000000000000000000000000000000000..e64b0b71a133805d9d4ec6ce851113c8497e3757 GIT binary patch literal 27 Zcmd;(U|{$U2ZbUg}ZnD0{|yc2Q2^q literal 0 HcmV?d00001 diff --git a/fuzz/test_cases/chanmon_consistency/crash-91ebb8583ed7705e2601334e52428ea5eb80a681 b/fuzz/test_cases/chanmon_consistency/crash-91ebb8583ed7705e2601334e52428ea5eb80a681 new file mode 100644 index 0000000000000000000000000000000000000000..ba9c42d7059ce40516c0cc28ac235e92721a4a98 GIT binary patch literal 23 dcmd~25 literal 0 HcmV?d00001 diff --git a/fuzz/test_cases/chanmon_consistency/fc_advance_before_drain_keyed_anchors b/fuzz/test_cases/chanmon_consistency/fc_advance_before_drain_keyed_anchors new file mode 100644 index 00000000000..a9b7f9e59f4 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/fc_advance_before_drain_keyed_anchors @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/fc_advance_before_drain_zero_fee_commitments b/fuzz/test_cases/chanmon_consistency/fc_advance_before_drain_zero_fee_commitments new file mode 100644 index 00000000000..5208796d906 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/fc_advance_before_drain_zero_fee_commitments @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/fc_after_claim_before_forward b/fuzz/test_cases/chanmon_consistency/fc_after_claim_before_forward new file mode 100644 index 0000000000000000000000000000000000000000..6ed4f13402c34dd77779be072fa58c2cb191520f GIT binary patch literal 18 acmZRu5tI~^msFHgSHE!M=B<18{sRCe_y81=B+z-?)(P;CAaO38!d-whW03k*O=Kufz literal 0 HcmV?d00001 diff --git a/fuzz/test_cases/chanmon_consistency/fc_direct_pay_claimed_keyed_anchors b/fuzz/test_cases/chanmon_consistency/fc_direct_pay_claimed_keyed_anchors new file mode 100644 index 00000000000..a3fb940a20a --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/fc_direct_pay_claimed_keyed_anchors @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/fc_direct_pay_claimed_zero_fee_commitments b/fuzz/test_cases/chanmon_consistency/fc_direct_pay_claimed_zero_fee_commitments new file mode 100644 index 00000000000..5363a35b90c --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/fc_direct_pay_claimed_zero_fee_commitments @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/fc_disabled_signers b/fuzz/test_cases/chanmon_consistency/fc_disabled_signers new file mode 100644 index 0000000000000000000000000000000000000000..e178523c474a4b925340f2c79d99e33af2b39852 GIT binary patch literal 13 VcmZQLaPY#Jvo~(ux^w5we*ixR3JCxJ literal 0 HcmV?d00001 diff --git a/fuzz/test_cases/chanmon_consistency/fc_disabled_signers_keyed_anchors b/fuzz/test_cases/chanmon_consistency/fc_disabled_signers_keyed_anchors new file mode 100644 index 00000000000..c3b358cff6e --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/fc_disabled_signers_keyed_anchors @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/fc_disabled_signers_zero_fee_commitments b/fuzz/test_cases/chanmon_consistency/fc_disabled_signers_zero_fee_commitments new file mode 100644 index 00000000000..7268091c3a6 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/fc_disabled_signers_zero_fee_commitments @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/fc_disconnect_close_reconnect b/fuzz/test_cases/chanmon_consistency/fc_disconnect_close_reconnect new file mode 100644 index 0000000000000000000000000000000000000000..207eb4b58c90a08f743da1baff7708c0e87a125f GIT binary patch literal 13 VcmZSJxxgnNapUH#J9qB<2LK~v2QdHu literal 0 HcmV?d00001 diff --git a/fuzz/test_cases/chanmon_consistency/fc_disconnect_close_reconnect_keyed_anchors b/fuzz/test_cases/chanmon_consistency/fc_disconnect_close_reconnect_keyed_anchors new file mode 100644 index 00000000000..519443e6590 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/fc_disconnect_close_reconnect_keyed_anchors @@ -0,0 +1 @@ +  \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/fc_disconnect_close_reconnect_zero_fee_commitments b/fuzz/test_cases/chanmon_consistency/fc_disconnect_close_reconnect_zero_fee_commitments new file mode 100644 index 00000000000..b89f4eb6bb1 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/fc_disconnect_close_reconnect_zero_fee_commitments @@ -0,0 +1 @@ +  \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/fc_disconnect_drain_reconnect b/fuzz/test_cases/chanmon_consistency/fc_disconnect_drain_reconnect new file mode 100644 index 0000000000000000000000000000000000000000..93ca334ca2a70442054b3a006617f9e744d02f78 GIT binary patch literal 13 VcmZR$z;ol~Ej|H>J9qB<2LLN42QdHu literal 0 HcmV?d00001 diff --git a/fuzz/test_cases/chanmon_consistency/fc_disconnect_drain_reconnect_keyed_anchors b/fuzz/test_cases/chanmon_consistency/fc_disconnect_drain_reconnect_keyed_anchors new file mode 100644 index 00000000000..977c994b110 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/fc_disconnect_drain_reconnect_keyed_anchors @@ -0,0 +1 @@ +  \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/fc_disconnect_drain_reconnect_zero_fee_commitments b/fuzz/test_cases/chanmon_consistency/fc_disconnect_drain_reconnect_zero_fee_commitments new file mode 100644 index 00000000000..c11308e079e --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/fc_disconnect_drain_reconnect_zero_fee_commitments @@ -0,0 +1 @@ +  \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/fc_drain_a_only b/fuzz/test_cases/chanmon_consistency/fc_drain_a_only new file mode 100644 index 0000000000000000000000000000000000000000..0b2e2d9f9ec9c46c34d8aca48afb11a0bac1872a GIT binary patch literal 7 OcmZR$aO2LMJO2R@+66oS literal 0 HcmV?d00001 diff --git a/fuzz/test_cases/chanmon_consistency/fc_drain_a_only_keyed_anchors b/fuzz/test_cases/chanmon_consistency/fc_drain_a_only_keyed_anchors new file mode 100644 index 00000000000..fb230c9d737 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/fc_drain_a_only_keyed_anchors @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/fc_drain_a_only_zero_fee_commitments b/fuzz/test_cases/chanmon_consistency/fc_drain_a_only_zero_fee_commitments new file mode 100644 index 00000000000..745714790dd --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/fc_drain_a_only_zero_fee_commitments @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/fc_during_reconnect b/fuzz/test_cases/chanmon_consistency/fc_during_reconnect new file mode 100644 index 0000000000000000000000000000000000000000..fa91c56dd57c27a7d09026c131bd07d60af03dc5 GIT binary patch literal 13 VcmZSJ;kzIpapUH#J9qB<2LK|X2QdHu literal 0 HcmV?d00001 diff --git a/fuzz/test_cases/chanmon_consistency/fc_during_reconnect_keyed_anchors b/fuzz/test_cases/chanmon_consistency/fc_during_reconnect_keyed_anchors new file mode 100644 index 00000000000..9d325043b08 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/fc_during_reconnect_keyed_anchors @@ -0,0 +1 @@ +  \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/fc_during_reconnect_zero_fee_commitments b/fuzz/test_cases/chanmon_consistency/fc_during_reconnect_zero_fee_commitments new file mode 100644 index 00000000000..5ed28383592 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/fc_during_reconnect_zero_fee_commitments @@ -0,0 +1 @@ +  \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/fc_dust_htlcs b/fuzz/test_cases/chanmon_consistency/fc_dust_htlcs new file mode 100644 index 0000000000000000000000000000000000000000..22e65fb3d4d29503573145a0fe55c4a1feb608ae GIT binary patch literal 19 bcmZRGh>VIBkPwhml)P~9#?4#z?)?Vrr(J9qB<2LL4m2S)$^ literal 0 HcmV?d00001 diff --git a/fuzz/test_cases/chanmon_consistency/fc_one_msg_at_a_time_keyed_anchors b/fuzz/test_cases/chanmon_consistency/fc_one_msg_at_a_time_keyed_anchors new file mode 100644 index 00000000000..44bfd92caef --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/fc_one_msg_at_a_time_keyed_anchors @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/fc_one_msg_at_a_time_zero_fee_commitments b/fuzz/test_cases/chanmon_consistency/fc_one_msg_at_a_time_zero_fee_commitments new file mode 100644 index 00000000000..69689cecd75 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/fc_one_msg_at_a_time_zero_fee_commitments @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/fc_pay_claim_close_pay b/fuzz/test_cases/chanmon_consistency/fc_pay_claim_close_pay new file mode 100644 index 0000000000000000000000000000000000000000..604a284a048544ac8c72956c8fd9b74698c31094 GIT binary patch literal 17 ZcmZQD5RecMlT*K7c;n`+J9qB<2LLL}2jc($ literal 0 HcmV?d00001 diff --git a/fuzz/test_cases/chanmon_consistency/fc_pay_claim_close_pay_keyed_anchors b/fuzz/test_cases/chanmon_consistency/fc_pay_claim_close_pay_keyed_anchors new file mode 100644 index 00000000000..813d703da99 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/fc_pay_claim_close_pay_keyed_anchors @@ -0,0 +1 @@ +0'1 \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/fc_pay_claim_close_pay_zero_fee_commitments b/fuzz/test_cases/chanmon_consistency/fc_pay_claim_close_pay_zero_fee_commitments new file mode 100644 index 00000000000..91a7822c238 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/fc_pay_claim_close_pay_zero_fee_commitments @@ -0,0 +1 @@ +0'1 \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/fc_pending_monitor b/fuzz/test_cases/chanmon_consistency/fc_pending_monitor new file mode 100644 index 0000000000000000000000000000000000000000..ed197c810f12c5699150264790926c8c73c95070 GIT binary patch literal 15 XcmZQzFc6Tqz`=Rr=B+z-?)(P;B=QGX literal 0 HcmV?d00001 diff --git a/fuzz/test_cases/chanmon_consistency/fc_pending_monitor_keyed_anchors b/fuzz/test_cases/chanmon_consistency/fc_pending_monitor_keyed_anchors new file mode 100644 index 0000000000000000000000000000000000000000..89b4d1eca9f4ba9a9d66aff40dfdbb854c3ad2b5 GIT binary patch literal 15 Xcmd;JFc6Tqz`=Rr=B+z-?)(P;C29v? literal 0 HcmV?d00001 diff --git a/fuzz/test_cases/chanmon_consistency/fc_pending_monitor_zero_fee_commitments b/fuzz/test_cases/chanmon_consistency/fc_pending_monitor_zero_fee_commitments new file mode 100644 index 0000000000000000000000000000000000000000..b7dfc3c50127d57805f2f588a8291985552c3165 GIT binary patch literal 15 XcmWe&Fc6Tqz`=Rr=B+z-?)(P;CE^EY literal 0 HcmV?d00001 diff --git a/fuzz/test_cases/chanmon_consistency/fc_rapid_fire b/fuzz/test_cases/chanmon_consistency/fc_rapid_fire new file mode 100644 index 0000000000000000000000000000000000000000..cf6df985d74d27e4fc3f6b1de87cb6f6a1d54b69 GIT binary patch literal 12 UcmZQDxL|bg#?4!I?%eqg04jk9A^-pY literal 0 HcmV?d00001 diff --git a/fuzz/test_cases/chanmon_consistency/fc_rapid_fire_keyed_anchors b/fuzz/test_cases/chanmon_consistency/fc_rapid_fire_keyed_anchors new file mode 100644 index 00000000000..01035d5c146 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/fc_rapid_fire_keyed_anchors @@ -0,0 +1 @@ +02 \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/fc_rapid_fire_zero_fee_commitments b/fuzz/test_cases/chanmon_consistency/fc_rapid_fire_zero_fee_commitments new file mode 100644 index 00000000000..a5d2a725f74 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/fc_rapid_fire_zero_fee_commitments @@ -0,0 +1 @@ +02 \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/fc_reconnect b/fuzz/test_cases/chanmon_consistency/fc_reconnect new file mode 100644 index 0000000000000000000000000000000000000000..d0a2f5dc15227c19183b0de7b4b7cfe8b0d099f6 GIT binary patch literal 13 VcmZR$z{4jXapUH#J9qB<2LL1}2QdHu literal 0 HcmV?d00001 diff --git a/fuzz/test_cases/chanmon_consistency/fc_reconnect_broadcast_announcements b/fuzz/test_cases/chanmon_consistency/fc_reconnect_broadcast_announcements new file mode 100644 index 00000000000..9547b711ba8 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/fc_reconnect_broadcast_announcements @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/fc_reconnect_keyed_anchors b/fuzz/test_cases/chanmon_consistency/fc_reconnect_keyed_anchors new file mode 100644 index 00000000000..77ac690a59a --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/fc_reconnect_keyed_anchors @@ -0,0 +1 @@ +  \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/fc_reconnect_zero_fee_commitments b/fuzz/test_cases/chanmon_consistency/fc_reconnect_zero_fee_commitments new file mode 100644 index 00000000000..a76076c83c3 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/fc_reconnect_zero_fee_commitments @@ -0,0 +1 @@ +  \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/fc_repeated_same_channel b/fuzz/test_cases/chanmon_consistency/fc_repeated_same_channel new file mode 100644 index 0000000000000000000000000000000000000000..2cecd41dd26ead926c526969e139f66f8969e77b GIT binary patch literal 11 TcmZR$aN)v@o44-Vx$_?YE%peH literal 0 HcmV?d00001 diff --git a/fuzz/test_cases/chanmon_consistency/fc_repeated_same_channel_keyed_anchors b/fuzz/test_cases/chanmon_consistency/fc_repeated_same_channel_keyed_anchors new file mode 100644 index 00000000000..9d1ae851c44 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/fc_repeated_same_channel_keyed_anchors @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/fc_repeated_same_channel_zero_fee_commitments b/fuzz/test_cases/chanmon_consistency/fc_repeated_same_channel_zero_fee_commitments new file mode 100644 index 00000000000..6fde55ccdb7 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/fc_repeated_same_channel_zero_fee_commitments @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/fc_restart_mid_resolve b/fuzz/test_cases/chanmon_consistency/fc_restart_mid_resolve new file mode 100644 index 0000000000000000000000000000000000000000..0517320bc3380cd876cedb26e79a836721db66e8 GIT binary patch literal 10 ScmZR$aO38!I~(rY`40dkd literal 0 HcmV?d00001 diff --git a/fuzz/test_cases/chanmon_consistency/force_close_basic_async b/fuzz/test_cases/chanmon_consistency/force_close_basic_async new file mode 100644 index 00000000000..086ce5b53fe --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/force_close_basic_async @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/force_close_basic_async_keyed_anchors b/fuzz/test_cases/chanmon_consistency/force_close_basic_async_keyed_anchors new file mode 100644 index 00000000000..55d8227b650 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/force_close_basic_async_keyed_anchors @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/force_close_basic_async_zero_fee_commitments b/fuzz/test_cases/chanmon_consistency/force_close_basic_async_zero_fee_commitments new file mode 100644 index 00000000000..4f375bcbcc8 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/force_close_basic_async_zero_fee_commitments @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/force_close_basic_keyed_anchors b/fuzz/test_cases/chanmon_consistency/force_close_basic_keyed_anchors new file mode 100644 index 00000000000..87788e516d1 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/force_close_basic_keyed_anchors @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/force_close_basic_zero_fee_commitments b/fuzz/test_cases/chanmon_consistency/force_close_basic_zero_fee_commitments new file mode 100644 index 00000000000..686c55e6e8d --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/force_close_basic_zero_fee_commitments @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/force_close_both_directions b/fuzz/test_cases/chanmon_consistency/force_close_both_directions new file mode 100644 index 0000000000000000000000000000000000000000..c55d73896f8652ff99bccefb8c203b6aca911c2a GIT binary patch literal 10 ScmZR$aPh{?TX*i<`40dk-3Pz` literal 0 HcmV?d00001 diff --git a/fuzz/test_cases/chanmon_consistency/force_close_both_directions_async b/fuzz/test_cases/chanmon_consistency/force_close_both_directions_async new file mode 100644 index 00000000000..4937e12b5e2 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/force_close_both_directions_async @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/force_close_both_directions_async_keyed_anchors b/fuzz/test_cases/chanmon_consistency/force_close_both_directions_async_keyed_anchors new file mode 100644 index 00000000000..868c75adb90 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/force_close_both_directions_async_keyed_anchors @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/force_close_both_directions_async_zero_fee_commitments b/fuzz/test_cases/chanmon_consistency/force_close_both_directions_async_zero_fee_commitments new file mode 100644 index 00000000000..0f3c204b38e --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/force_close_both_directions_async_zero_fee_commitments @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/force_close_both_directions_keyed_anchors b/fuzz/test_cases/chanmon_consistency/force_close_both_directions_keyed_anchors new file mode 100644 index 00000000000..f5fd80c55fb --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/force_close_both_directions_keyed_anchors @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/force_close_both_directions_zero_fee_commitments b/fuzz/test_cases/chanmon_consistency/force_close_both_directions_zero_fee_commitments new file mode 100644 index 00000000000..d22536577df --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/force_close_both_directions_zero_fee_commitments @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/force_close_htlc_needs_height b/fuzz/test_cases/chanmon_consistency/force_close_htlc_needs_height new file mode 100644 index 0000000000000000000000000000000000000000..9936534a4758c909218399db60fdfb75b12faeaa GIT binary patch literal 11 ScmZQD5RecMle=)^<}CmX83XtL literal 0 HcmV?d00001 diff --git a/fuzz/test_cases/chanmon_consistency/force_close_htlc_needs_height_async b/fuzz/test_cases/chanmon_consistency/force_close_htlc_needs_height_async new file mode 100644 index 00000000000..11a097db35c --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/force_close_htlc_needs_height_async @@ -0,0 +1 @@ +0   \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/force_close_htlc_needs_height_async_keyed_anchors b/fuzz/test_cases/chanmon_consistency/force_close_htlc_needs_height_async_keyed_anchors new file mode 100644 index 00000000000..846b036955d --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/force_close_htlc_needs_height_async_keyed_anchors @@ -0,0 +1 @@ +0   \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/force_close_htlc_needs_height_async_zero_fee_commitments b/fuzz/test_cases/chanmon_consistency/force_close_htlc_needs_height_async_zero_fee_commitments new file mode 100644 index 00000000000..2ba86884cb3 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/force_close_htlc_needs_height_async_zero_fee_commitments @@ -0,0 +1 @@ +0   \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/force_close_htlc_needs_height_keyed_anchors b/fuzz/test_cases/chanmon_consistency/force_close_htlc_needs_height_keyed_anchors new file mode 100644 index 00000000000..7e59be8feaa --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/force_close_htlc_needs_height_keyed_anchors @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/force_close_htlc_needs_height_zero_fee_commitments b/fuzz/test_cases/chanmon_consistency/force_close_htlc_needs_height_zero_fee_commitments new file mode 100644 index 00000000000..d852e18e121 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/force_close_htlc_needs_height_zero_fee_commitments @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/force_close_htlc_resolved b/fuzz/test_cases/chanmon_consistency/force_close_htlc_resolved new file mode 100644 index 0000000000000000000000000000000000000000..73b498f3c647ce882aa42003e39a1c5e1633509a GIT binary patch literal 11 ScmZQD5RecMle=)^-aP;fCj$t11JCh literal 0 HcmV?d00001 diff --git a/fuzz/test_cases/chanmon_consistency/force_close_three_node_preimage_async b/fuzz/test_cases/chanmon_consistency/force_close_three_node_preimage_async new file mode 100644 index 00000000000..f2961702e79 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/force_close_three_node_preimage_async @@ -0,0 +1,4 @@ +<   + ! + '' ! + \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/force_close_three_node_preimage_async_keyed_anchors b/fuzz/test_cases/chanmon_consistency/force_close_three_node_preimage_async_keyed_anchors new file mode 100644 index 00000000000..babb7953068 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/force_close_three_node_preimage_async_keyed_anchors @@ -0,0 +1,4 @@ +<   + ! + '' ! + \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/force_close_three_node_preimage_async_zero_fee_commitments b/fuzz/test_cases/chanmon_consistency/force_close_three_node_preimage_async_zero_fee_commitments new file mode 100644 index 00000000000..10fdc572a11 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/force_close_three_node_preimage_async_zero_fee_commitments @@ -0,0 +1,4 @@ +<   + ! + '' ! + \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/force_close_three_node_preimage_keyed_anchors b/fuzz/test_cases/chanmon_consistency/force_close_three_node_preimage_keyed_anchors new file mode 100644 index 00000000000..d0d5126f551 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/force_close_three_node_preimage_keyed_anchors @@ -0,0 +1 @@ +<!''! \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/force_close_three_node_preimage_zero_fee_commitments b/fuzz/test_cases/chanmon_consistency/force_close_three_node_preimage_zero_fee_commitments new file mode 100644 index 00000000000..50ba2ea9183 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/force_close_three_node_preimage_zero_fee_commitments @@ -0,0 +1 @@ +<!''! \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/ldk_crash_channelmanager_19484 b/fuzz/test_cases/chanmon_consistency/ldk_crash_channelmanager_19484 new file mode 100644 index 00000000000..577f5f71cd0 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/ldk_crash_channelmanager_19484 @@ -0,0 +1 @@ +p \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/ldk_crash_channelmanager_9836 b/fuzz/test_cases/chanmon_consistency/ldk_crash_channelmanager_9836 new file mode 100644 index 0000000000000000000000000000000000000000..14487ba70cd7971f641f8961d0d9e4dd2362284f GIT binary patch literal 13 UcmZRu5tI~^msFHgSHJfk01M#*6#xJL literal 0 HcmV?d00001 diff --git a/fuzz/test_cases/chanmon_consistency/ldk_crash_channelmonitor_2727 b/fuzz/test_cases/chanmon_consistency/ldk_crash_channelmonitor_2727 new file mode 100644 index 00000000000..a1852414d9c --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/ldk_crash_channelmonitor_2727 @@ -0,0 +1 @@ +@ \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/ldk_crash_onchaintx_1025 b/fuzz/test_cases/chanmon_consistency/ldk_crash_onchaintx_1025 new file mode 100644 index 0000000000000000000000000000000000000000..982ac748a974a353683dce6ccbfd5bc4b1b935f3 GIT binary patch literal 14 VcmZRu5tO`e@Be>EMag?={{bmn25A5Q literal 0 HcmV?d00001 diff --git a/fuzz/test_cases/chanmon_consistency/ldk_crash_onchaintx_913 b/fuzz/test_cases/chanmon_consistency/ldk_crash_onchaintx_913 new file mode 100644 index 00000000000..6c32f6a71aa --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/ldk_crash_onchaintx_913 @@ -0,0 +1 @@ +ppqp \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/ldk_crash_signer_395 b/fuzz/test_cases/chanmon_consistency/ldk_crash_signer_395 new file mode 100644 index 00000000000..2fb6ec95740 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/ldk_crash_signer_395 @@ -0,0 +1 @@ +"qqqqqqq޵ \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/timeout-0103befb3dc5aa050668752668d04e85bd1fc14e b/fuzz/test_cases/chanmon_consistency/timeout-0103befb3dc5aa050668752668d04e85bd1fc14e new file mode 100644 index 0000000000000000000000000000000000000000..f5c015c80af9ac48a7b07e6750ceb9a37f6bf2b2 GIT binary patch literal 24 gcmZRu5R_!F5tI~^msFHYxTnVOf6d0N_x}F}06Yx{od5s; literal 0 HcmV?d00001 diff --git a/fuzz/test_cases/chanmon_consistency/timeout-05fc1bb98f2a3b29e826a4de636474de0b23c895 b/fuzz/test_cases/chanmon_consistency/timeout-05fc1bb98f2a3b29e826a4de636474de0b23c895 new file mode 100644 index 0000000000000000000000000000000000000000..e63b29d1bd588160c7f213a43693cce2daca74dd GIT binary patch literal 25 YcmdXK2aVHS}C$T2~(EtCl|D*q-v9$l8g%2YD literal 0 HcmV?d00001 diff --git a/fuzz/test_cases/chanmon_consistency/timeout-87b55c5b37383fe43420089fd3e8ccecbb034b44 b/fuzz/test_cases/chanmon_consistency/timeout-87b55c5b37383fe43420089fd3e8ccecbb034b44 new file mode 100644 index 00000000000..a9600d0cbf4 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/timeout-87b55c5b37383fe43420089fd3e8ccecbb034b44 @@ -0,0 +1 @@ +@: !<:: !' !'' \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/timeout-885f446335ae279baed408d42af8c398dfdb8c9b b/fuzz/test_cases/chanmon_consistency/timeout-885f446335ae279baed408d42af8c398dfdb8c9b new file mode 100644 index 00000000000..b9ee3e25f13 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/timeout-885f446335ae279baed408d42af8c398dfdb8c9b @@ -0,0 +1 @@ +88888' \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/timeout-8a81e4c066465a2975ef22625c0b91da6332a2c8 b/fuzz/test_cases/chanmon_consistency/timeout-8a81e4c066465a2975ef22625c0b91da6332a2c8 new file mode 100644 index 0000000000000000000000000000000000000000..735c8da14eb48e8954d925975ba2a408f04c127e GIT binary patch literal 24 fcmdm@7{j^QF#cm literal 0 HcmV?d00001 diff --git a/fuzz/test_cases/chanmon_consistency/timeout-8f2aebf3aeeb70d8edd39a886e30beb770f3b42b b/fuzz/test_cases/chanmon_consistency/timeout-8f2aebf3aeeb70d8edd39a886e30beb770f3b42b new file mode 100644 index 0000000000000000000000000000000000000000..d20f7e163f8421195cbda7574a6d90d16792e144 GIT binary patch literal 23 dcmZRu5tI~^{|^L`it3W;>KAU@ymjy1e*kHe3vmDd literal 0 HcmV?d00001 diff --git a/fuzz/test_cases/chanmon_consistency/timeout-91d97d9eea2bd59f746681ad822488262e832ff1 b/fuzz/test_cases/chanmon_consistency/timeout-91d97d9eea2bd59f746681ad822488262e832ff1 new file mode 100644 index 0000000000000000000000000000000000000000..77a897d89de4a5e40c5c3a1035890a603fc64c3b GIT binary patch literal 26 acmZRu5tI~^{|^U}ijo&^+`O}5#eV?7*$|BY literal 0 HcmV?d00001 diff --git a/fuzz/test_cases/chanmon_consistency/timeout-95a90908391d3398084b77eb11ff5c9d7fdde008 b/fuzz/test_cases/chanmon_consistency/timeout-95a90908391d3398084b77eb11ff5c9d7fdde008 new file mode 100644 index 0000000000000000000000000000000000000000..d85e723aaa1295d624ed77e1a22fcfb8a6042a74 GIT binary patch literal 24 gcmZRu5R_y%At)&*FR3V*a8Hfl|C)_k@BRM|07h{MDF6Tf literal 0 HcmV?d00001 diff --git a/fuzz/test_cases/chanmon_consistency/timeout-a31cdfc423211489c841a6ddd067f9e6cf5bed4b b/fuzz/test_cases/chanmon_consistency/timeout-a31cdfc423211489c841a6ddd067f9e6cf5bed4b new file mode 100644 index 0000000000000000000000000000000000000000..d0e80d1f6497081170669bf2b7504fd06ab7f028 GIT binary patch literal 21 dcmZSJ6SR_-RFqU#zxD5)*Z=LCkM8ex2LMDL2;Bex literal 0 HcmV?d00001 diff --git a/fuzz/test_cases/chanmon_consistency/timeout-b1c4840ea1279dd8d6080d79373ae55bbcad3061 b/fuzz/test_cases/chanmon_consistency/timeout-b1c4840ea1279dd8d6080d79373ae55bbcad3061 new file mode 100644 index 0000000000000000000000000000000000000000..50a625cb0ed0e5bea991996d2115a46175e284f4 GIT binary patch literal 25 ccmZRu`TzgFiJ+vQyriO}`Yn-r|Jfh_0CJB9Hvj+t literal 0 HcmV?d00001 diff --git a/fuzz/test_cases/chanmon_consistency/timeout-b6ef84eec94d70bbc385c98c4ab0bac77da00a2f b/fuzz/test_cases/chanmon_consistency/timeout-b6ef84eec94d70bbc385c98c4ab0bac77da00a2f new file mode 100644 index 00000000000..59319574a0e --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/timeout-b6ef84eec94d70bbc385c98c4ab0bac77da00a2f @@ -0,0 +1 @@ +<: !''<: !'' \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/timeout-bae8693182b102dfebab143a0f48992dad76245d b/fuzz/test_cases/chanmon_consistency/timeout-bae8693182b102dfebab143a0f48992dad76245d new file mode 100644 index 0000000000000000000000000000000000000000..db79f209c2272dcb30fa1f220e3371a6dde9c440 GIT binary patch literal 23 dcmZRu5tI~^{|^L`it3W;>KAUP-@142KLBPL3cmmV literal 0 HcmV?d00001 diff --git a/fuzz/test_cases/chanmon_consistency/timeout-bcab049322729e275e3bbdacebc633495da7643f b/fuzz/test_cases/chanmon_consistency/timeout-bcab049322729e275e3bbdacebc633495da7643f new file mode 100644 index 0000000000000000000000000000000000000000..d774ccb3b2d39456f76b9e4ffa3e96ed2bd46038 GIT binary patch literal 26 icmZRu5tI}Zw33%plvG#0bx+Z&;QyBGk{55>`wswBRtg^g literal 0 HcmV?d00001 diff --git a/fuzz/test_cases/chanmon_consistency/timeout-d5afdff02a253c9f2fbce95cbaf730eb210128fa b/fuzz/test_cases/chanmon_consistency/timeout-d5afdff02a253c9f2fbce95cbaf730eb210128fa new file mode 100644 index 00000000000..7b8cb7b07ab --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/timeout-d5afdff02a253c9f2fbce95cbaf730eb210128fa @@ -0,0 +1 @@ +888888' \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/timeout-d6494f068fb2b2d31f1ac8627752692b3c8b7d2f b/fuzz/test_cases/chanmon_consistency/timeout-d6494f068fb2b2d31f1ac8627752692b3c8b7d2f new file mode 100644 index 00000000000..ee690802eff --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/timeout-d6494f068fb2b2d31f1ac8627752692b3c8b7d2f @@ -0,0 +1 @@ +<<!!R \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/timeout-edd3f8168217501dd93f3c24d09c2c095cdf7784 b/fuzz/test_cases/chanmon_consistency/timeout-edd3f8168217501dd93f3c24d09c2c095cdf7784 new file mode 100644 index 0000000000000000000000000000000000000000..9d78147299487bab8c60a29b319b80926ca57ae8 GIT binary patch literal 14 VcmZRu5tI~^msFHgza?_-KL8LR1Ka=r literal 0 HcmV?d00001 diff --git a/fuzz/test_cases/chanmon_consistency/timeout-fcbcc131184e33d5b000820b0972f6197b0801d2 b/fuzz/test_cases/chanmon_consistency/timeout-fcbcc131184e33d5b000820b0972f6197b0801d2 new file mode 100644 index 00000000000..df65f08fe80 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/timeout-fcbcc131184e33d5b000820b0972f6197b0801d2 @@ -0,0 +1 @@ +<: !''<: !'' \ No newline at end of file From 6a5c428817b5b9e2979f72fb951adade1d33846b Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Sat, 18 Apr 2026 13:39:53 +0200 Subject: [PATCH 09/12] force-close: tighten restart and dust invariants Track in-progress monitor snapshots through restarts and settle claimed payments according to the force-close dust outcome on used paths. Fix the P2WPKH witness estimate for HTLC bump fee checks and replace the ad hoc crash corpus with named representative regressions. AI tools were used in preparing this commit. --- fuzz/FC-INFO.md | 28 +- fuzz/src/chanmon_consistency.rs | 569 +++++++++++++++--- .../fc_bump_htlc_p2wpkh_fee_estimate | 1 + .../fc_claimed_dust_htlc_sender_fails | Bin 0 -> 8 bytes .../fc_claimed_mpp_dust_path_still_succeeds | 1 + ...start_claimed_payment_stale_monitor_replay | 1 + .../fc_restart_in_progress_chain_sync_replay | 1 + lightning/src/events/bump_transaction/mod.rs | 12 +- lightning/src/sign/mod.rs | 10 + 9 files changed, 509 insertions(+), 114 deletions(-) create mode 100644 fuzz/test_cases/chanmon_consistency/fc_bump_htlc_p2wpkh_fee_estimate create mode 100644 fuzz/test_cases/chanmon_consistency/fc_claimed_dust_htlc_sender_fails create mode 100644 fuzz/test_cases/chanmon_consistency/fc_claimed_mpp_dust_path_still_succeeds create mode 100644 fuzz/test_cases/chanmon_consistency/fc_restart_claimed_payment_stale_monitor_replay create mode 100644 fuzz/test_cases/chanmon_consistency/fc_restart_in_progress_chain_sync_replay diff --git a/fuzz/FC-INFO.md b/fuzz/FC-INFO.md index 1c8983603e7..a83fc591225 100644 --- a/fuzz/FC-INFO.md +++ b/fuzz/FC-INFO.md @@ -10,6 +10,8 @@ Force-close fuzzing here should: - exercise realistic off-chain to on-chain transitions - keep force-close from changing the eventual outcome of claimed payments +- classify claimed-payment sender failures only when force-close dust on used + channels blocks every remaining completion path for the claimed payment - allow unclaimed HTLCs to resolve by CLTV timeout - drive the harness far enough that it observes real terminal outcomes - avoid manufacturing timeout wins by starving message delivery or claim @@ -21,9 +23,11 @@ The current hard mode is: - once the harness calls `claim_funds`, that HTLC must eventually produce `PaymentClaimed` at the receiver -- every such claimed HTLC must also eventually produce `PaymentSent` at the - sender -- going on-chain does not create an exception to those two requirements +- if no force-close dust on used channels blocks every completion path for the + claimed payment, the sender must eventually produce `PaymentSent` +- if force-close dust on used channels does block every completion path for the + claimed payment, the sender must eventually produce `PaymentFailed` +- going on-chain does not create any broader exception than that dust case - unclaimed HTLCs may still fail by CLTV expiry - CSV waits on force-close outputs are normal and expected; they are not payment outcome changes @@ -33,8 +37,10 @@ The current hard mode is: In this mode, the following are harness failures: - `HTLCHandlingFailed::Receive` after we already chose to claim the HTLC -- a receiver-side claim without a later sender-side `PaymentSent` -- a claimed HTLC resolving by CLTV timeout instead of fulfillment +- a receiver-side claim without the receiver later getting `PaymentClaimed` +- a claimed HTLC without the correct sender-side terminal event for its + force-close dust classification +- a claimed HTLC that should fulfill resolving by CLTV timeout instead - cleanup stopping while live balances or other pending work still show that more progress is possible @@ -46,7 +52,7 @@ Do not conflate CSV and CLTV: - CLTV expiry changes the HTLC outcome The harness should keep driving through CSV waits. It should only protect -claimed HTLCs from CLTV-expiry resolution. +claimed HTLCs that should still fulfill from CLTV-expiry resolution. ## Harness Rules @@ -58,11 +64,14 @@ The main rules for preserving the invariant are: transactions, so preimages can propagate before timeout paths win - keep sender-side payment bookkeeping independent of `list_recent_payments()` +- track which channels each payment actually used, and when force-closing, + snapshot which used payment paths become dust-blocked on the closer's + commitment - keep driving while `ClaimableOnChannelClose`, HTLC-related claimable balances, queued messages, pending monitor updates, or pending broadcasts still show unresolved work - only stop before a CLTV boundary when crossing it would let a claimed HTLC - expire instead of fulfill + that should still fulfill expire instead - do not hide pending-payment state behind unrelated auto-driving before an explicit force-close opcode; a bounded pre-close drain is acceptable when it is only making already-queued work visible @@ -71,7 +80,10 @@ The main rules for preserving the invariant are: When changing this harness, verify: -- claimed HTLCs still require both `PaymentClaimed` and `PaymentSent` +- claimed HTLCs still require `PaymentClaimed` +- claimed HTLCs only require `PaymentFailed` when all used completion paths are + blocked by force-close dust +- all other claimed HTLCs still require `PaymentSent` - unclaimed HTLCs may still time out on-chain - force-close opcodes still act on the currently pending state - large synthetic height jumps do not become blind timeout buttons again diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 22cdbfa726d..bdce3817e98 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -51,7 +51,7 @@ use lightning::events::{self, EventsProvider}; use lightning::ln::channel::{ FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE, MAX_STD_OUTPUT_DUST_LIMIT_SATOSHIS, }; -use lightning::ln::channel_state::ChannelDetails; +use lightning::ln::channel_state::{ChannelDetails, InboundHTLCDetails, OutboundHTLCDetails}; use lightning::ln::channelmanager::{ ChainParameters, ChannelManager, ChannelManagerReadArgs, PaymentId, RecentPaymentDetails, TrustedChannelFeatures, @@ -91,7 +91,6 @@ use lightning::events::bump_transaction::sync::BumpTransactionEventHandlerSync; use lightning_invoice::RawBolt11Invoice; use crate::utils::test_logger::{self, Output}; -use crate::utils::test_persister::TestPersister; use bitcoin::secp256k1::ecdh::SharedSecret; use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature}; @@ -108,6 +107,7 @@ use std::sync::atomic; use std::sync::{Arc, Mutex}; const MAX_FEE: u32 = 10_000; + struct FuzzEstimator { ret_val: atomic::AtomicU32, } @@ -283,6 +283,12 @@ impl Writer for VecWriter { } } +fn serialize_monitor(monitor: &ChannelMonitor) -> Vec { + let mut ser = VecWriter(Vec::new()); + monitor.write(&mut ser).unwrap(); + ser.0 +} + /// The LDK API requires that any time we tell it we're done persisting a `ChannelMonitor[Update]` /// we never pass it in as the "latest" `ChannelMonitor` on startup. However, we can pass /// out-of-date monitors as long as we never told LDK we finished persisting them, which we do by @@ -304,10 +310,58 @@ struct LatestMonitorState { pending_monitors: Vec<(u64, Vec)>, } +struct HarnessPersister { + pub update_ret: Mutex, + pub latest_monitors: Arc>>, +} +impl chainmonitor::Persist for HarnessPersister { + fn persist_new_channel( + &self, _monitor_name: lightning::util::persist::MonitorName, + _data: &channelmonitor::ChannelMonitor, + ) -> chain::ChannelMonitorUpdateStatus { + self.update_ret.lock().unwrap().clone() + } + + fn update_persisted_channel( + &self, _monitor_name: lightning::util::persist::MonitorName, + update: Option<&channelmonitor::ChannelMonitorUpdate>, + data: &channelmonitor::ChannelMonitor, + ) -> chain::ChannelMonitorUpdateStatus { + let status = self.update_ret.lock().unwrap().clone(); + if update.is_none() { + if let Some(state) = self.latest_monitors.lock().unwrap().get_mut(&data.channel_id()) { + let monitor_id = data.get_latest_update_id(); + let serialized_monitor = serialize_monitor(data); + match status { + chain::ChannelMonitorUpdateStatus::Completed => { + state.pending_monitors.retain(|(id, _)| *id != monitor_id); + state.persisted_monitor_id = monitor_id; + state.persisted_monitor = serialized_monitor; + }, + chain::ChannelMonitorUpdateStatus::InProgress => { + if let Some((_, pending_monitor)) = + state.pending_monitors.iter_mut().find(|(id, _)| *id == monitor_id) + { + *pending_monitor = serialized_monitor; + } else { + state.pending_monitors.push((monitor_id, serialized_monitor)); + state.pending_monitors.sort_by_key(|(id, _)| *id); + } + }, + chain::ChannelMonitorUpdateStatus::UnrecoverableError => {}, + } + } + } + status + } + + fn archive_persisted_channel(&self, _monitor_name: lightning::util::persist::MonitorName) {} +} + struct TestChainMonitor { pub logger: Arc, pub keys: Arc, - pub persister: Arc, + pub persister: Arc, pub chain_monitor: Arc< chainmonitor::ChainMonitor< TestChannelSigner, @@ -315,17 +369,22 @@ struct TestChainMonitor { Arc, Arc, Arc, - Arc, + Arc, Arc, >, >, - pub latest_monitors: Mutex>, + pub latest_monitors: Arc>>, } impl TestChainMonitor { pub fn new( broadcaster: Arc, logger: Arc, feeest: Arc, - persister: Arc, keys: Arc, + initial_update_ret: ChannelMonitorUpdateStatus, keys: Arc, ) -> Self { + let latest_monitors = Arc::new(Mutex::new(new_hash_map())); + let persister = Arc::new(HarnessPersister { + update_ret: Mutex::new(initial_update_ret), + latest_monitors: Arc::clone(&latest_monitors), + }); Self { chain_monitor: Arc::new(chainmonitor::ChainMonitor::new( None, @@ -340,7 +399,7 @@ impl TestChainMonitor { logger, keys, persister, - latest_monitors: Mutex::new(new_hash_map()), + latest_monitors, } } } @@ -348,20 +407,19 @@ impl chain::Watch for TestChainMonitor { fn watch_channel( &self, channel_id: ChannelId, monitor: channelmonitor::ChannelMonitor, ) -> Result { - let mut ser = VecWriter(Vec::new()); - monitor.write(&mut ser).unwrap(); + let ser = serialize_monitor(&monitor); let monitor_id = monitor.get_latest_update_id(); let res = self.chain_monitor.watch_channel(channel_id, monitor); let state = match res { Ok(chain::ChannelMonitorUpdateStatus::Completed) => LatestMonitorState { persisted_monitor_id: monitor_id, - persisted_monitor: ser.0, + persisted_monitor: ser, pending_monitors: Vec::new(), }, Ok(chain::ChannelMonitorUpdateStatus::InProgress) => LatestMonitorState { persisted_monitor_id: monitor_id, persisted_monitor: Vec::new(), - pending_monitors: vec![(monitor_id, ser.0)], + pending_monitors: vec![(monitor_id, ser)], }, Ok(chain::ChannelMonitorUpdateStatus::UnrecoverableError) => panic!(), Err(()) => panic!(), @@ -398,16 +456,15 @@ impl chain::Watch for TestChainMonitor { &self.logger, ) .unwrap(); - let mut ser = VecWriter(Vec::new()); - deserialized_monitor.write(&mut ser).unwrap(); + let ser = serialize_monitor(&deserialized_monitor); let res = self.chain_monitor.update_channel(channel_id, update); match res { chain::ChannelMonitorUpdateStatus::Completed => { map_entry.persisted_monitor_id = update.update_id; - map_entry.persisted_monitor = ser.0; + map_entry.persisted_monitor = ser; }, chain::ChannelMonitorUpdateStatus::InProgress => { - map_entry.pending_monitors.push((update.update_id, ser.0)); + map_entry.pending_monitors.push((update.update_id, ser)); }, chain::ChannelMonitorUpdateStatus::UnrecoverableError => panic!(), } @@ -619,6 +676,16 @@ type ChanMan<'a> = ChannelManager< Arc, >; +#[inline] +fn inbound_dust_blocks_path(htlc: &InboundHTLCDetails) -> bool { + htlc.is_dust +} + +#[inline] +fn outbound_dust_blocks_path(htlc: &OutboundHTLCDetails) -> bool { + htlc.is_dust +} + #[inline] fn get_payment_secret_hash( dest: &ChanMan, payment_ctr: &mut u64, @@ -992,9 +1059,7 @@ pub fn do_test(data: &[u8], out: Out) { $broadcaster.clone(), logger.clone(), $fee_estimator.clone(), - Arc::new(TestPersister { - update_ret: Mutex::new(mon_style[$node_id as usize].borrow().clone()), - }), + mon_style[$node_id as usize].borrow().clone(), Arc::clone(&keys_manager), )); @@ -1049,7 +1114,7 @@ pub fn do_test(data: &[u8], out: Out) { let reload_node = |ser: &Vec, node_id: u8, old_monitors: &TestChainMonitor, - mut use_old_mons, + mut use_old_mons: usize, keys, fee_estimator, broadcaster: Arc| { @@ -1060,9 +1125,7 @@ pub fn do_test(data: &[u8], out: Out) { broadcaster.clone(), logger.clone(), Arc::clone(fee_estimator), - Arc::new(TestPersister { - update_ret: Mutex::new(ChannelMonitorUpdateStatus::Completed), - }), + ChannelMonitorUpdateStatus::Completed, Arc::clone(keys), )); @@ -1700,22 +1763,71 @@ pub fn do_test(data: &[u8], out: Out) { let receiver_claimed_payment_hashes: RefCell> = RefCell::new(HashSet::new()); let sender_sent_payment_hashes: RefCell> = RefCell::new(HashSet::new()); + let sender_failed_payment_hashes: RefCell> = RefCell::new(HashSet::new()); + let payment_hashes_by_id: RefCell> = + RefCell::new(new_hash_map()); + let payment_paths_by_hash: RefCell>>> = + RefCell::new(new_hash_map()); + let blocked_dust_paths_by_hash: RefCell>> = + RefCell::new(new_hash_map()); let payment_preimages: RefCell> = RefCell::new(new_hash_map()); let closed_channels: RefCell> = RefCell::new(HashSet::new()); + let register_payment = |source_idx: usize, + payment_id: PaymentId, + payment_hash: PaymentHash, + payment_paths: Vec>| { + assert!( + payment_hashes_by_id.borrow_mut().insert(payment_id, payment_hash).is_none(), + "duplicate payment_id {:?}", + payment_id + ); + assert!( + payment_paths_by_hash.borrow_mut().insert(payment_hash, payment_paths).is_none(), + "duplicate payment_hash {:?}", + payment_hash + ); + pending_payments.borrow_mut()[source_idx].push(payment_id); + }; + let claim_expects_sender_failure = |hash: &PaymentHash| { + let payment_paths = payment_paths_by_hash.borrow(); + let Some(paths) = payment_paths.get(hash) else { + return false; + }; + if paths.is_empty() { + return false; + } + blocked_dust_paths_by_hash + .borrow() + .get(hash) + .is_some_and(|blocked_paths| blocked_paths.len() == paths.len()) + }; let summarize_claim_tracking = || -> String { let claim_requested = claimed_payment_hashes.borrow(); let receiver_claimed = receiver_claimed_payment_hashes.borrow(); let sender_sent = sender_sent_payment_hashes.borrow(); + let sender_failed = sender_failed_payment_hashes.borrow(); + let expected_failed_count = + claim_requested.iter().filter(|hash| claim_expects_sender_failure(hash)).count(); let missing_receiver = claim_requested.iter().filter(|hash| !receiver_claimed.contains(*hash)).count(); - let missing_sender = - claim_requested.iter().filter(|hash| !sender_sent.contains(*hash)).count(); + let missing_sender = claim_requested + .iter() + .filter(|hash| { + if claim_expects_sender_failure(hash) { + !sender_failed.contains(*hash) + } else { + !sender_sent.contains(*hash) + } + }) + .count(); format!( - "claims requested={} receiver_claimed={} sender_sent={} missing_receiver={} missing_sender={}", + "claims requested={} receiver_claimed={} sender_sent={} sender_failed={} expected_failed={} missing_receiver={} missing_sender={}", claim_requested.len(), receiver_claimed.len(), sender_sent.len(), + sender_failed.len(), + expected_failed_count, missing_receiver, missing_sender, ) @@ -1724,9 +1836,15 @@ pub fn do_test(data: &[u8], out: Out) { let claim_requested = claimed_payment_hashes.borrow(); let receiver_claimed = receiver_claimed_payment_hashes.borrow(); let sender_sent = sender_sent_payment_hashes.borrow(); - claim_requested - .iter() - .any(|hash| !receiver_claimed.contains(hash) || !sender_sent.contains(hash)) + let sender_failed = sender_failed_payment_hashes.borrow(); + claim_requested.iter().any(|hash| { + !receiver_claimed.contains(hash) + || if claim_expects_sender_failure(hash) { + !sender_failed.contains(hash) + } else { + !sender_sent.contains(hash) + } + }) }; let has_live_payment_work = || { pending_payments.borrow().iter().any(|payments| !payments.is_empty()) @@ -1776,6 +1894,48 @@ pub fn do_test(data: &[u8], out: Out) { }}; } + macro_rules! record_force_close_dust { + ($closer_idx: expr, $channel_id: expr) => {{ + if let Some(channel) = nodes[$closer_idx] + .list_channels() + .into_iter() + .find(|chan| chan.channel_id == $channel_id) + { + let mut dust_parts = channel + .pending_inbound_htlcs + .iter() + .filter(|htlc| inbound_dust_blocks_path(htlc)) + .map(|htlc| (htlc.payment_hash, htlc.amount_msat)) + .chain( + channel + .pending_outbound_htlcs + .iter() + .filter(|htlc| outbound_dust_blocks_path(htlc)) + .map(|htlc| (htlc.payment_hash, htlc.amount_msat)), + ) + .collect::>(); + let payment_paths = payment_paths_by_hash.borrow(); + let mut blocked_paths = blocked_dust_paths_by_hash.borrow_mut(); + for (payment_hash, amount_msat) in dust_parts.drain(..) { + let Some(paths) = payment_paths.get(&payment_hash) else { + continue; + }; + let blocked_for_hash = + blocked_paths.entry(payment_hash).or_insert_with(HashSet::new); + if let Some((path_idx, _)) = + paths.iter().enumerate().find(|(path_idx, path)| { + !blocked_for_hash.contains(path_idx) + && path.iter().any(|(chan_id, part_amt)| { + *chan_id == $channel_id && *part_amt == amount_msat + }) + }) { + blocked_for_hash.insert(path_idx); + } + } + } + }}; + } + let splice_channel = |node: &ChanMan, counterparty_node_id: &PublicKey, @@ -2376,8 +2536,24 @@ pub fn do_test(data: &[u8], out: Out) { assert!(resolved_payment_ids[$node].contains(&payment_id)); } }, - events::Event::PaymentFailed { payment_id, .. } - | events::Event::ProbeFailed { payment_id, .. } => { + events::Event::PaymentFailed { payment_id, payment_hash, .. } => { + if let Some(payment_hash) = payment_hash + .or_else(|| payment_hashes_by_id.borrow().get(&payment_id).copied()) + { + sender_failed_payment_hashes.borrow_mut().insert(payment_hash); + } + let idx_opt = + pending_payments[$node].iter().position(|id| *id == payment_id); + if let Some(idx) = idx_opt { + pending_payments[$node].remove(idx); + resolved_payment_ids[$node].insert(payment_id); + } else if !resolved_payment_ids[$node].contains(&payment_id) { + // Payment failed immediately on send, so it was never added to + // pending_payments. Record its terminal payment_id anyway. + resolved_payment_ids[$node].insert(payment_id); + } + }, + events::Event::ProbeFailed { payment_id, .. } => { let idx_opt = pending_payments[$node].iter().position(|id| *id == payment_id); if let Some(idx) = idx_opt { @@ -2520,7 +2696,7 @@ pub fn do_test(data: &[u8], out: Out) { id.0[0..8].copy_from_slice(&payment_ctr.to_ne_bytes()); let succeeded = send_payment(source, dest, dest_chan_id, amt, secret, hash, id); if succeeded { - pending_payments.borrow_mut()[source_idx].push(id); + register_payment(source_idx, id, hash, vec![vec![(dest_chan_id, amt)]]); } succeeded }; @@ -2546,6 +2722,7 @@ pub fn do_test(data: &[u8], out: Out) { let (secret, hash) = get_payment_secret_hash(dest, payment_ctr, &payment_preimages); let mut id = PaymentId([0; 32]); id.0[0..8].copy_from_slice(&payment_ctr.to_ne_bytes()); + let first_hop_fee = 50_000; let succeeded = send_hop_payment( source, middle, @@ -2558,7 +2735,12 @@ pub fn do_test(data: &[u8], out: Out) { id, ); if succeeded { - pending_payments.borrow_mut()[source_idx].push(id); + register_payment( + source_idx, + id, + hash, + vec![vec![(middle_chan_id, amt + first_hop_fee), (dest_chan_id, amt)]], + ); } }; @@ -2584,7 +2766,22 @@ pub fn do_test(data: &[u8], out: Out) { let succeeded = send_mpp_payment(source, dest, &live_dest_chan_ids, amt, secret, hash, id); if succeeded { - pending_payments.borrow_mut()[source_idx].push(id); + let num_paths = live_dest_chan_ids.len(); + let amt_per_path = amt / num_paths as u64; + let payment_paths = live_dest_chan_ids + .iter() + .copied() + .enumerate() + .map(|(i, chan_id)| { + let path_amt = if i == num_paths - 1 { + amt - amt_per_path * (num_paths as u64 - 1) + } else { + amt_per_path + }; + vec![(chan_id, path_amt)] + }) + .collect(); + register_payment(source_idx, id, hash, payment_paths); } }; @@ -2615,6 +2812,10 @@ pub fn do_test(data: &[u8], out: Out) { let (secret, hash) = get_payment_secret_hash(dest, payment_ctr, &payment_preimages); let mut id = PaymentId([0; 32]); id.0[0..8].copy_from_slice(&payment_ctr.to_ne_bytes()); + let num_paths = live_middle_chan_ids.len().max(live_dest_chan_ids.len()); + let first_hop_fee = 50_000; + let amt_per_path = amt / num_paths as u64; + let fee_per_path = first_hop_fee / num_paths as u64; let succeeded = send_mpp_hop_payment( source, middle, @@ -2627,7 +2828,24 @@ pub fn do_test(data: &[u8], out: Out) { id, ); if succeeded { - pending_payments.borrow_mut()[source_idx].push(id); + let payment_paths = (0..num_paths) + .map(|i| { + let middle_chan_id = live_middle_chan_ids[i % live_middle_chan_ids.len()]; + let dest_chan_id = live_dest_chan_ids[i % live_dest_chan_ids.len()]; + let path_amt = if i == num_paths - 1 { + amt - amt_per_path * (num_paths as u64 - 1) + } else { + amt_per_path + }; + let path_fee = if i == num_paths - 1 { + first_hop_fee - fee_per_path * (num_paths as u64 - 1) + } else { + fee_per_path + }; + vec![(middle_chan_id, path_amt + path_fee), (dest_chan_id, path_amt)] + }) + .collect(); + register_payment(source_idx, id, hash, payment_paths); } }; @@ -3341,7 +3559,7 @@ pub fn do_test(data: &[u8], out: Out) { &node_a_ser, 0, &monitor_a, - v, + v as usize, &keys_manager_a, &fee_est_a, broadcast_a.clone(), @@ -3370,7 +3588,7 @@ pub fn do_test(data: &[u8], out: Out) { &node_b_ser, 1, &monitor_b, - v, + v as usize, &keys_manager_b, &fee_est_b, broadcast_b.clone(), @@ -3395,7 +3613,7 @@ pub fn do_test(data: &[u8], out: Out) { &node_c_ser, 2, &monitor_c, - v, + v as usize, &keys_manager_c, &fee_est_c, broadcast_c.clone(), @@ -3474,6 +3692,7 @@ pub fn do_test(data: &[u8], out: Out) { // pre-close drain keeps those already-issued effects visible without // also overriding signer-stall fuzzing via unblock_all_signers!. flush_progress!(32); + record_force_close_dust!(0, chan_a_id); if nodes[0] .force_close_broadcasting_latest_txn( &chan_a_id, @@ -3492,6 +3711,7 @@ pub fn do_test(data: &[u8], out: Out) { }, 0xd1 => { flush_progress!(32); + record_force_close_dust!(1, chan_b_id); if nodes[1] .force_close_broadcasting_latest_txn( &chan_b_id, @@ -3506,6 +3726,7 @@ pub fn do_test(data: &[u8], out: Out) { }, 0xd2 => { flush_progress!(32); + record_force_close_dust!(1, chan_a_id); if nodes[1] .force_close_broadcasting_latest_txn( &chan_a_id, @@ -3520,6 +3741,7 @@ pub fn do_test(data: &[u8], out: Out) { }, 0xd3 => { flush_progress!(32); + record_force_close_dust!(2, chan_b_id); if nodes[2] .force_close_broadcasting_latest_txn( &chan_b_id, @@ -3669,51 +3891,120 @@ pub fn do_test(data: &[u8], out: Out) { nodes[1].signer_unblocked(None); nodes[2].signer_unblocked(None); - // Restarts may intentionally reload an older persisted monitor. - // Before settling, catch those monitors up to the harness' - // current height without rewinding the `ChannelManager`. This - // preserves the stale-monitor replay scenario while avoiding a - // fake reorg inside the node itself. - for (monitor, node_height) in [ - (&monitor_a, &node_height_a), - (&monitor_b, &node_height_b), - (&monitor_c, &node_height_c), - ] { - let mut min_monitor_height = *node_height; - for chan_id in monitor.chain_monitor.list_monitors() { - if let Ok(mon) = monitor.chain_monitor.get_monitor(chan_id) { - min_monitor_height = - std::cmp::min(min_monitor_height, mon.current_best_block().height); - } - } - let mut h = min_monitor_height; - while h < *node_height { - let mut next_height = h + 1; - while next_height <= *node_height - && chain_state.block_at(next_height).1.is_empty() - { - next_height += 1; - } - if next_height > *node_height { - h = *node_height; - let (header, _) = chain_state.block_at(h); - monitor.chain_monitor.best_block_updated(header, h); - break; - } - if next_height > h + 1 { - h = next_height - 1; - let (header, _) = chain_state.block_at(h); - monitor.chain_monitor.best_block_updated(header, h); + macro_rules! process_messages_and_events_only { + () => {{ + let mut settled = false; + let mut last_pass_no_updates = false; + for _ in 0..100 { + let mut completed_monitor_update = false; + for id in &chan_ab_ids { + completed_monitor_update |= + complete_all_monitor_updates(&monitor_a, id); + completed_monitor_update |= + complete_all_monitor_updates(&monitor_b, id); + } + for id in &chan_bc_ids { + completed_monitor_update |= + complete_all_monitor_updates(&monitor_b, id); + completed_monitor_update |= + complete_all_monitor_updates(&monitor_c, id); + } + + let mut had_msg_or_ev = false; + let node0_msgs = + process_msg_events!(0, false, ProcessMessages::AllMessages); + if node0_msgs { + had_msg_or_ev = true; + } + let node1_msgs = + process_msg_events!(1, false, ProcessMessages::AllMessages); + if node1_msgs { + had_msg_or_ev = true; + } + let node2_msgs = + process_msg_events!(2, false, ProcessMessages::AllMessages); + if node2_msgs { + had_msg_or_ev = true; + } + let node0_evs = process_events!(0, false); + if node0_evs { + had_msg_or_ev = true; + } + let node1_evs = process_events!(1, false); + if node1_evs { + had_msg_or_ev = true; + } + let node2_evs = process_events!(2, false); + if node2_evs { + had_msg_or_ev = true; + } + + if completed_monitor_update || had_msg_or_ev { + last_pass_no_updates = false; + continue; + } + if last_pass_no_updates { + settled = true; + break; + } + last_pass_no_updates = true; } - h = next_height; - let (header, txn) = chain_state.block_at(h); - let txdata: Vec<_> = - txn.iter().enumerate().map(|(i, tx)| (i + 1, tx)).collect(); - if !txdata.is_empty() { - monitor.chain_monitor.transactions_confirmed(header, &txdata, h); + assert!( + settled, + "message-only settle exceeded budget: {}", + pending_work_summary!() + ); + }}; + } + + macro_rules! catch_up_raw_monitors { + () => {{ + for (monitor, node_height) in [ + (&monitor_a, &node_height_a), + (&monitor_b, &node_height_b), + (&monitor_c, &node_height_c), + ] { + let mut min_monitor_height = *node_height; + for chan_id in monitor.chain_monitor.list_monitors() { + if let Ok(mon) = monitor.chain_monitor.get_monitor(chan_id) { + min_monitor_height = std::cmp::min( + min_monitor_height, + mon.current_best_block().height, + ); + } + } + let mut h = min_monitor_height; + while h < *node_height { + let mut next_height = h + 1; + while next_height <= *node_height + && chain_state.block_at(next_height).1.is_empty() + { + next_height += 1; + } + if next_height > *node_height { + h = *node_height; + let (header, _) = chain_state.block_at(h); + monitor.chain_monitor.best_block_updated(header, h); + break; + } + if next_height > h + 1 { + h = next_height - 1; + let (header, _) = chain_state.block_at(h); + monitor.chain_monitor.best_block_updated(header, h); + } + h = next_height; + let (header, txn) = chain_state.block_at(h); + let txdata: Vec<_> = + txn.iter().enumerate().map(|(i, tx)| (i + 1, tx)).collect(); + if !txdata.is_empty() { + monitor + .chain_monitor + .transactions_confirmed(header, &txdata, h); + } + monitor.chain_monitor.best_block_updated(header, h); + } } - monitor.chain_monitor.best_block_updated(header, h); - } + }}; } macro_rules! process_all_events { @@ -3906,6 +4197,32 @@ pub fn do_test(data: &[u8], out: Out) { } }; } + // Restarts may intentionally reload an older persisted monitor. + // First, let ChannelManager replay any missing monitor updates + // without advancing the chain. Only once those updates quiesce do + // we replay missed blocks into the raw monitors. Otherwise a stale + // monitor can reach the current tip before it knows about the + // force-close or preimage update it needed to interpret an already + // confirmed HTLC spend. + let has_stale_raw_monitors = [ + (&monitor_a, node_height_a), + (&monitor_b, node_height_b), + (&monitor_c, node_height_c), + ] + .into_iter() + .any(|(monitor, node_height)| { + monitor.chain_monitor.list_monitors().into_iter().any(|chan_id| { + monitor + .chain_monitor + .get_monitor(chan_id) + .map(|mon| mon.current_best_block().height < node_height) + .unwrap_or(false) + }) + }); + if has_stale_raw_monitors { + process_messages_and_events_only!(); + catch_up_raw_monitors!(); + } process_all_events!(); // Since MPP payments are supported, we wait until we fully settle the state of all @@ -3921,10 +4238,13 @@ pub fn do_test(data: &[u8], out: Out) { // observed. We advance only one block at a time, draining before and // after each block so cleanup cannot manufacture timeout wins. // - // Hard mode only forbids CLTV-expiry resolution for payments we - // explicitly claimed. Unclaimed HTLCs may still legitimately time - // out on-chain, so we only stop before an expiry boundary when doing - // so would be required to preserve a claimed payment's fulfillment. + // Hard mode only forbids CLTV-expiry resolution for claimed + // payments that should still fulfill. If a force-close on a used + // channel trimmed one of the payment's HTLCs as dust, the sender is + // expected to fail instead. Unclaimed HTLCs may also legitimately + // time out on-chain, so we only stop before an expiry boundary when + // doing so would be required to preserve a claimed payment's + // fulfillment. if !closed_channels.borrow().is_empty() { for _ in 0..4096 { flush_progress!(32); @@ -3959,17 +4279,29 @@ pub fn do_test(data: &[u8], out: Out) { timeout_height, payment_hash, .. - } if claimed_hashes.contains(payment_hash) => Some(*timeout_height), + } if claimed_hashes.contains(payment_hash) + && !claim_expects_sender_failure(payment_hash) => + { + Some(*timeout_height) + }, Balance::MaybeTimeoutClaimableHTLC { claimable_height, payment_hash, .. - } if claimed_hashes.contains(payment_hash) => Some(*claimable_height), + } if claimed_hashes.contains(payment_hash) + && !claim_expects_sender_failure(payment_hash) => + { + Some(*claimable_height) + }, Balance::MaybePreimageClaimableHTLC { expiry_height, payment_hash, .. - } if claimed_hashes.contains(payment_hash) => Some(*expiry_height), + } if claimed_hashes.contains(payment_hash) + && !claim_expects_sender_failure(payment_hash) => + { + Some(*expiry_height) + }, _ => None, }) .min() @@ -4004,8 +4336,8 @@ pub fn do_test(data: &[u8], out: Out) { ); } - // Verify that every HTLC we explicitly claimed reached both terminal - // payment events, at the receiver and back at the sender. + // Verify that every HTLC we explicitly claimed reached the receiver + // claim event, then the correct sender-side terminal event. let claimed_hashes = claimed_payment_hashes.borrow().iter().copied().collect::>(); for hash in claimed_hashes { @@ -4018,22 +4350,61 @@ pub fn do_test(data: &[u8], out: Out) { pending_work_summary!(), ); let sender_saw_sent = sender_sent_payment_hashes.borrow().contains(&hash); - assert!( - sender_saw_sent, - "Payment {:?} was claimed with claim_funds but sender never got PaymentSent: {}", - hash, - pending_work_summary!(), - ); + let sender_saw_failed = sender_failed_payment_hashes.borrow().contains(&hash); + if claim_expects_sender_failure(&hash) { + assert!( + sender_saw_failed, + "Payment {:?} was claimed with claim_funds on a dust-trimmed force-close path but sender never got PaymentFailed: {}", + hash, + pending_work_summary!(), + ); + assert!( + !sender_saw_sent, + "Payment {:?} was claimed with claim_funds on a dust-trimmed force-close path but sender got PaymentSent: {}", + hash, + pending_work_summary!(), + ); + } else { + assert!( + sender_saw_sent, + "Payment {:?} was claimed with claim_funds but sender never got PaymentSent: {}", + hash, + pending_work_summary!(), + ); + } } + let mut can_send_after_settle = + |source_idx: usize, + dest_idx: usize, + dest_chan_id: ChannelId, + amt, + payment_ctr: &mut u64| { + if closed_channels.borrow().contains(&dest_chan_id) { + return false; + } + let source = &nodes[source_idx]; + let dest = &nodes[dest_idx]; + let (secret, hash) = + get_payment_secret_hash(dest, payment_ctr, &payment_preimages); + let mut id = PaymentId([0; 32]); + id.0[0..8].copy_from_slice(&payment_ctr.to_ne_bytes()); + let succeeded = + send_payment(source, dest, dest_chan_id, amt, secret, hash, id); + if succeeded { + register_payment(source_idx, id, hash, vec![vec![(dest_chan_id, amt)]]); + } + succeeded + }; + // Finally, make sure that at least one end of each channel can make a substantial payment for &chan_id in &chan_ab_ids { if closed_channels.borrow().contains(&chan_id) { continue; } assert!( - send(0, 1, chan_id, 10_000_000, &mut p_ctr) - || send(1, 0, chan_id, 10_000_000, &mut p_ctr) + can_send_after_settle(0, 1, chan_id, 10_000_000, &mut p_ctr) + || can_send_after_settle(1, 0, chan_id, 10_000_000, &mut p_ctr) ); } for &chan_id in &chan_bc_ids { @@ -4041,8 +4412,8 @@ pub fn do_test(data: &[u8], out: Out) { continue; } assert!( - send(1, 2, chan_id, 10_000_000, &mut p_ctr) - || send(2, 1, chan_id, 10_000_000, &mut p_ctr) + can_send_after_settle(1, 2, chan_id, 10_000_000, &mut p_ctr) + || can_send_after_settle(2, 1, chan_id, 10_000_000, &mut p_ctr) ); } diff --git a/fuzz/test_cases/chanmon_consistency/fc_bump_htlc_p2wpkh_fee_estimate b/fuzz/test_cases/chanmon_consistency/fc_bump_htlc_p2wpkh_fee_estimate new file mode 100644 index 00000000000..7ca75067697 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/fc_bump_htlc_p2wpkh_fee_estimate @@ -0,0 +1 @@ +q= signed_tx_weight); // When fuzzing, signatures are trivially small so the actual weight can be // significantly less than estimated. Skip the lower-bound check. @@ -702,7 +702,7 @@ impl= signed_tx_weight); // When fuzzing, signatures are trivially small so the actual weight can be // significantly less than estimated. Skip the lower-bound check. - #[cfg(not(fuzzing))] assert!(expected_signed_tx_weight * 98 / 100 <= signed_tx_weight); let expected_signed_tx_fee = diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index 3237149338b..733efb3f640 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -135,6 +135,7 @@ impl_writeable_tlv_based!(DelayedPaymentOutputDescriptor, { pub(crate) const P2WPKH_WITNESS_WEIGHT: u64 = (1 /* witness items */ + 1 /* sig push */ + MAX_STANDARD_SIGNATURE_SIZE + + 1 /* sighash flag */ + 1 /* pubkey push */ + COMPRESSED_PUBLIC_KEY_SIZE) as u64; @@ -2713,6 +2714,15 @@ pub fn dyn_sign() { let _signer: Box; } +#[test] +pub fn p2wpkh_witness_weight_matches_serialized_witness() { + let witness = Witness::from_slice(&[ + vec![0; MAX_STANDARD_SIGNATURE_SIZE + 1], + vec![0; COMPRESSED_PUBLIC_KEY_SIZE], + ]); + assert_eq!(witness.size() as u64, P2WPKH_WITNESS_WEIGHT); +} + #[cfg(ldk_bench)] pub mod benches { use crate::sign::{EntropySource, KeysManager}; From 6bfd334f0e91b74d9acf43a3362f22f7e59e2906 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Sat, 18 Apr 2026 19:23:06 +0200 Subject: [PATCH 10/12] Relax claimed dust-path completion invariant Allow claimed payments on dust-touched force-close paths to end in either PaymentSent or PaymentFailed, while still requiring PaymentClaimed and a sender terminal outcome. Update FC-INFO and OPEN-ISSUES to match the new invariant and the remaining targeted repro set. Verified with rl-tools fuzz runner: - run-1776524390: 8 ok, 0 failed - run-1776524663: 1 ok, 5 failed --- fuzz/FC-INFO.md | 31 +++-- fuzz/OPEN-ISSUES.md | 106 ++++++++++++++++++ fuzz/src/chanmon_consistency.rs | 90 +++++++-------- ...tious_claim_stuck_after_force_close_218996 | 1 + ...tious_claim_stuck_after_force_close_36a22e | 1 + ...tious_claim_stuck_after_force_close_d7793e | 1 + ...ing_claim_request_after_force_close_39b47f | 1 + ...ing_claim_request_after_force_close_ed278d | 1 + ...t_path_claim_expected_fail_but_sent_5099d3 | 1 + ...t_path_claim_expected_fail_but_sent_595140 | 1 + ...t_path_claim_expected_fail_but_sent_7a4062 | 1 + ...t_path_claim_expected_fail_but_sent_9d7311 | 1 + ...t_path_claim_expected_fail_but_sent_b1281e | 1 + ...t_path_claim_expected_fail_but_sent_bf210c | 1 + ..._monitor_update_replay_out_of_order_dcbc86 | 1 + 15 files changed, 177 insertions(+), 62 deletions(-) create mode 100644 fuzz/OPEN-ISSUES.md create mode 100644 fuzz/test_cases/chanmon_consistency/fc_contentious_claim_stuck_after_force_close_218996 create mode 100644 fuzz/test_cases/chanmon_consistency/fc_contentious_claim_stuck_after_force_close_36a22e create mode 100644 fuzz/test_cases/chanmon_consistency/fc_contentious_claim_stuck_after_force_close_d7793e create mode 100644 fuzz/test_cases/chanmon_consistency/fc_duplicate_pending_claim_request_after_force_close_39b47f create mode 100644 fuzz/test_cases/chanmon_consistency/fc_duplicate_pending_claim_request_after_force_close_ed278d create mode 100644 fuzz/test_cases/chanmon_consistency/fc_dust_path_claim_expected_fail_but_sent_5099d3 create mode 100644 fuzz/test_cases/chanmon_consistency/fc_dust_path_claim_expected_fail_but_sent_595140 create mode 100644 fuzz/test_cases/chanmon_consistency/fc_dust_path_claim_expected_fail_but_sent_7a4062 create mode 100644 fuzz/test_cases/chanmon_consistency/fc_dust_path_claim_expected_fail_but_sent_9d7311 create mode 100644 fuzz/test_cases/chanmon_consistency/fc_dust_path_claim_expected_fail_but_sent_b1281e create mode 100644 fuzz/test_cases/chanmon_consistency/fc_dust_path_claim_expected_fail_but_sent_bf210c create mode 100644 fuzz/test_cases/chanmon_consistency/fc_monitor_update_replay_out_of_order_dcbc86 diff --git a/fuzz/FC-INFO.md b/fuzz/FC-INFO.md index a83fc591225..1293fcdcedb 100644 --- a/fuzz/FC-INFO.md +++ b/fuzz/FC-INFO.md @@ -10,8 +10,8 @@ Force-close fuzzing here should: - exercise realistic off-chain to on-chain transitions - keep force-close from changing the eventual outcome of claimed payments -- classify claimed-payment sender failures only when force-close dust on used - channels blocks every remaining completion path for the claimed payment +- only allow claimed-payment sender failures when force-close dust touched a + used payment path - allow unclaimed HTLCs to resolve by CLTV timeout - drive the harness far enough that it observes real terminal outcomes - avoid manufacturing timeout wins by starving message delivery or claim @@ -23,10 +23,14 @@ The current hard mode is: - once the harness calls `claim_funds`, that HTLC must eventually produce `PaymentClaimed` at the receiver -- if no force-close dust on used channels blocks every completion path for the - claimed payment, the sender must eventually produce `PaymentSent` -- if force-close dust on used channels does block every completion path for the - claimed payment, the sender must eventually produce `PaymentFailed` +- after that claim, the sender must eventually produce a terminal outcome, + `PaymentSent` or `PaymentFailed` +- if the sender produces `PaymentFailed` for a claimed payment, some used + force-close path for that payment must have been dust-trimmed +- force-close dust on a used path is not, by itself, enough to require + `PaymentFailed`; the payment may still end in `PaymentSent` +- if no used force-close path for the claimed payment was dust-trimmed, the + sender must eventually produce `PaymentSent` - going on-chain does not create any broader exception than that dust case - unclaimed HTLCs may still fail by CLTV expiry - CSV waits on force-close outputs are normal and expected; they are not @@ -38,8 +42,9 @@ In this mode, the following are harness failures: - `HTLCHandlingFailed::Receive` after we already chose to claim the HTLC - a receiver-side claim without the receiver later getting `PaymentClaimed` -- a claimed HTLC without the correct sender-side terminal event for its - force-close dust classification +- a claimed HTLC without any sender-side terminal event +- a claimed HTLC getting `PaymentFailed` without any dust-trimmed used + force-close path - a claimed HTLC that should fulfill resolving by CLTV timeout instead - cleanup stopping while live balances or other pending work still show that more progress is possible @@ -71,7 +76,7 @@ The main rules for preserving the invariant are: queued messages, pending monitor updates, or pending broadcasts still show unresolved work - only stop before a CLTV boundary when crossing it would let a claimed HTLC - that should still fulfill expire instead + that has not yet reached a sender terminal event expire instead - do not hide pending-payment state behind unrelated auto-driving before an explicit force-close opcode; a bounded pre-close drain is acceptable when it is only making already-queued work visible @@ -81,9 +86,11 @@ The main rules for preserving the invariant are: When changing this harness, verify: - claimed HTLCs still require `PaymentClaimed` -- claimed HTLCs only require `PaymentFailed` when all used completion paths are - blocked by force-close dust -- all other claimed HTLCs still require `PaymentSent` +- claimed HTLCs still require a sender-side terminal event +- claimed HTLCs only allow `PaymentFailed` when some used force-close path was + dust-trimmed +- claimed HTLCs without dust-trimmed used force-close paths still require + `PaymentSent` - unclaimed HTLCs may still time out on-chain - force-close opcodes still act on the currently pending state - large synthetic height jumps do not become blind timeout buttons again diff --git a/fuzz/OPEN-ISSUES.md b/fuzz/OPEN-ISSUES.md new file mode 100644 index 00000000000..af4eafb8774 --- /dev/null +++ b/fuzz/OPEN-ISSUES.md @@ -0,0 +1,106 @@ +# Open Issues + +This file tracks the currently failing `chanmon_consistency` cases after the +latest focused dust-invariant fix and targeted reruns. + +Current reference run: +`fuzz/artifacts/chanmon_runner/run-1776524663/summary.txt` + +Current renamed failing cases: + +- `fc_contentious_claim_stuck_after_force_close_218996` +- `fc_contentious_claim_stuck_after_force_close_36a22e` +- `fc_contentious_claim_stuck_after_force_close_d7793e` +- `fc_duplicate_pending_claim_request_after_force_close_39b47f` +- `fc_duplicate_pending_claim_request_after_force_close_ed278d` + +## Resolved Recently + +- Dust-path sender-failure prediction was too strong. + The targeted rerun + `fuzz/artifacts/chanmon_runner/run-1776524390/summary.txt` + is now clean for: + `fc_claimed_dust_htlc_sender_fails`, + `fc_claimed_mpp_dust_path_still_succeeds`, + and the six `fc_dust_path_claim_expected_fail_but_sent_*` cases. + The harness now treats force-close dust on a used path as making + `PaymentFailed` permissible, not mandatory. + +- Live monitor reconstruction no longer replays updates out of order. + `fc_monitor_update_replay_out_of_order_dcbc86` now passes in + `fuzz/artifacts/chanmon_runner/run-1776524663/summary.txt`. + +## 1. Duplicate pending claim request after force-close + +Cases: + +- `fc_duplicate_pending_claim_request_after_force_close_39b47f` +- `fc_duplicate_pending_claim_request_after_force_close_ed278d` + +Current behavior: + +- The same delayed claim package is yielded twice, then + `lightning/src/chain/onchaintx.rs:913` trips while registering the + duplicate claim request. + +Why this may still be harness fallout: + +- These cases also involve restart and in-flight monitor snapshot + handling. +- If the harness rebuilds the live monitor from stale state, it can + plausibly re-yield the same pending on-chain claim event. + +Relevant evidence: + +- In the representative log + `fuzz/artifacts/chanmon_runner/run-1776524663/logs/fc_duplicate_pending_claim_request_after_force_close_39b47f.log`, + the same pair of outpoints is yielded twice at the same height before + the panic. + +Current conclusion: + +- These survive the live-monitor reconstruction fix. +- The duplicate claim request is now a real remaining bug candidate, + likely in or around `OnchainTxHandler` delayed-claim replay. + +## 2. Claimed payment can get stuck behind a contentious later claim tx + +Cases: + +- `fc_contentious_claim_stuck_after_force_close_218996` +- `fc_contentious_claim_stuck_after_force_close_36a22e` +- `fc_contentious_claim_stuck_after_force_close_d7793e` + +Current behavior: + +- The final harness settle phase ends with a claimed payment still + pending. +- The sender never reaches `PaymentSent`. +- The closing node still reports a `contentious` balance, and the next + hop still reports `maybe_timeout`. + +Why this may be a real LDK bug: + +- In the representative log + `fuzz/artifacts/chanmon_runner/run-1776524663/logs/fc_contentious_claim_stuck_after_force_close_218996.log`, + node `1` first confirms a single-input HTLC claim transaction for one + output, then later builds a second HTLC claim transaction that + includes that already-spent output again together with another HTLC + output. +- Once the earlier tx has confirmed, the later two-input tx is no + longer confirmable. +- The payment then remains stuck in the exact state the hard invariant + is supposed to rule out. + +Current conclusion: + +- This is the strongest remaining candidate for a real LDK bug. +- It still reproduces after the live-monitor reconstruction fix, so the + stale-monitor explanation is no longer enough. + +## Next steps + +1. Investigate why delayed claim packages are yielded twice in the two + `fc_duplicate_pending_claim_request_after_force_close_*` cases. +2. Reduce the three `fc_contentious_claim_stuck_after_force_close_*` + cases to a minimal repro showing the later unconfirmable batch claim. diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index bdce3817e98..76dacef378b 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -435,12 +435,15 @@ impl chain::Watch for TestChainMonitor { ) -> chain::ChannelMonitorUpdateStatus { let mut map_lock = self.latest_monitors.lock().unwrap(); let map_entry = map_lock.get_mut(&channel_id).expect("Didn't have monitor on update call"); - let latest_monitor_data = map_entry - .pending_monitors - .last() - .as_ref() - .map(|(_, data)| data) - .unwrap_or(&map_entry.persisted_monitor); + let latest_monitor_data = match map_entry.pending_monitors.last() { + Some((pending_id, pending_data)) + if *pending_id > map_entry.persisted_monitor_id + || map_entry.persisted_monitor.is_empty() => + { + pending_data + }, + _ => &map_entry.persisted_monitor, + }; let deserialized_monitor = <(BestBlock, channelmonitor::ChannelMonitor)>::read( &mut &latest_monitor_data[..], @@ -1789,45 +1792,32 @@ pub fn do_test(data: &[u8], out: Out) { ); pending_payments.borrow_mut()[source_idx].push(payment_id); }; - let claim_expects_sender_failure = |hash: &PaymentHash| { - let payment_paths = payment_paths_by_hash.borrow(); - let Some(paths) = payment_paths.get(hash) else { - return false; - }; - if paths.is_empty() { - return false; - } + let claim_allows_sender_failure = |hash: &PaymentHash| { blocked_dust_paths_by_hash .borrow() .get(hash) - .is_some_and(|blocked_paths| blocked_paths.len() == paths.len()) + .is_some_and(|blocked_paths| !blocked_paths.is_empty()) }; let summarize_claim_tracking = || -> String { let claim_requested = claimed_payment_hashes.borrow(); let receiver_claimed = receiver_claimed_payment_hashes.borrow(); let sender_sent = sender_sent_payment_hashes.borrow(); let sender_failed = sender_failed_payment_hashes.borrow(); - let expected_failed_count = - claim_requested.iter().filter(|hash| claim_expects_sender_failure(hash)).count(); + let failure_allowed_count = + claim_requested.iter().filter(|hash| claim_allows_sender_failure(hash)).count(); let missing_receiver = claim_requested.iter().filter(|hash| !receiver_claimed.contains(*hash)).count(); let missing_sender = claim_requested .iter() - .filter(|hash| { - if claim_expects_sender_failure(hash) { - !sender_failed.contains(*hash) - } else { - !sender_sent.contains(*hash) - } - }) + .filter(|hash| !sender_sent.contains(*hash) && !sender_failed.contains(*hash)) .count(); format!( - "claims requested={} receiver_claimed={} sender_sent={} sender_failed={} expected_failed={} missing_receiver={} missing_sender={}", + "claims requested={} receiver_claimed={} sender_sent={} sender_failed={} failure_allowed={} missing_receiver={} missing_sender={}", claim_requested.len(), receiver_claimed.len(), sender_sent.len(), sender_failed.len(), - expected_failed_count, + failure_allowed_count, missing_receiver, missing_sender, ) @@ -1839,11 +1829,7 @@ pub fn do_test(data: &[u8], out: Out) { let sender_failed = sender_failed_payment_hashes.borrow(); claim_requested.iter().any(|hash| { !receiver_claimed.contains(hash) - || if claim_expects_sender_failure(hash) { - !sender_failed.contains(hash) - } else { - !sender_sent.contains(hash) - } + || (!sender_sent.contains(hash) && !sender_failed.contains(hash)) }) }; let has_live_payment_work = || { @@ -4270,6 +4256,8 @@ pub fn do_test(data: &[u8], out: Out) { let can_drive_more_cleanup = has_cleanup_balances || has_pending_work!(); let next_claimed_htlc_boundary = { let claimed_hashes = claimed_payment_hashes.borrow(); + let sender_sent = sender_sent_payment_hashes.borrow(); + let sender_failed = sender_failed_payment_hashes.borrow(); balances_a .iter() .chain(balances_b.iter()) @@ -4280,7 +4268,8 @@ pub fn do_test(data: &[u8], out: Out) { payment_hash, .. } if claimed_hashes.contains(payment_hash) - && !claim_expects_sender_failure(payment_hash) => + && !sender_sent.contains(payment_hash) + && !sender_failed.contains(payment_hash) => { Some(*timeout_height) }, @@ -4289,7 +4278,8 @@ pub fn do_test(data: &[u8], out: Out) { payment_hash, .. } if claimed_hashes.contains(payment_hash) - && !claim_expects_sender_failure(payment_hash) => + && !sender_sent.contains(payment_hash) + && !sender_failed.contains(payment_hash) => { Some(*claimable_height) }, @@ -4298,7 +4288,8 @@ pub fn do_test(data: &[u8], out: Out) { payment_hash, .. } if claimed_hashes.contains(payment_hash) - && !claim_expects_sender_failure(payment_hash) => + && !sender_sent.contains(payment_hash) + && !sender_failed.contains(payment_hash) => { Some(*expiry_height) }, @@ -4351,23 +4342,22 @@ pub fn do_test(data: &[u8], out: Out) { ); let sender_saw_sent = sender_sent_payment_hashes.borrow().contains(&hash); let sender_saw_failed = sender_failed_payment_hashes.borrow().contains(&hash); - if claim_expects_sender_failure(&hash) { - assert!( - sender_saw_failed, - "Payment {:?} was claimed with claim_funds on a dust-trimmed force-close path but sender never got PaymentFailed: {}", - hash, - pending_work_summary!(), - ); - assert!( - !sender_saw_sent, - "Payment {:?} was claimed with claim_funds on a dust-trimmed force-close path but sender got PaymentSent: {}", - hash, - pending_work_summary!(), - ); - } else { + assert!( + !(sender_saw_sent && sender_saw_failed), + "Payment {:?} was claimed with claim_funds but sender got both PaymentSent and PaymentFailed: {}", + hash, + pending_work_summary!(), + ); + assert!( + sender_saw_sent || sender_saw_failed, + "Payment {:?} was claimed with claim_funds but sender never got a terminal outcome: {}", + hash, + pending_work_summary!(), + ); + if sender_saw_failed { assert!( - sender_saw_sent, - "Payment {:?} was claimed with claim_funds but sender never got PaymentSent: {}", + claim_allows_sender_failure(&hash), + "Payment {:?} was claimed with claim_funds and sender got PaymentFailed without any dust-trimmed used force-close path: {}", hash, pending_work_summary!(), ); diff --git a/fuzz/test_cases/chanmon_consistency/fc_contentious_claim_stuck_after_force_close_218996 b/fuzz/test_cases/chanmon_consistency/fc_contentious_claim_stuck_after_force_close_218996 new file mode 100644 index 00000000000..f9704181dfd --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/fc_contentious_claim_stuck_after_force_close_218996 @@ -0,0 +1 @@ +== \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/fc_contentious_claim_stuck_after_force_close_36a22e b/fuzz/test_cases/chanmon_consistency/fc_contentious_claim_stuck_after_force_close_36a22e new file mode 100644 index 00000000000..a703a6a2114 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/fc_contentious_claim_stuck_after_force_close_36a22e @@ -0,0 +1 @@ +,== \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/fc_contentious_claim_stuck_after_force_close_d7793e b/fuzz/test_cases/chanmon_consistency/fc_contentious_claim_stuck_after_force_close_d7793e new file mode 100644 index 00000000000..3d1ffd6db4f --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/fc_contentious_claim_stuck_after_force_close_d7793e @@ -0,0 +1 @@ +v== \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/fc_duplicate_pending_claim_request_after_force_close_39b47f b/fuzz/test_cases/chanmon_consistency/fc_duplicate_pending_claim_request_after_force_close_39b47f new file mode 100644 index 00000000000..04b31004a35 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/fc_duplicate_pending_claim_request_after_force_close_39b47f @@ -0,0 +1 @@ +ssв \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/fc_duplicate_pending_claim_request_after_force_close_ed278d b/fuzz/test_cases/chanmon_consistency/fc_duplicate_pending_claim_request_after_force_close_ed278d new file mode 100644 index 00000000000..dad6421e159 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/fc_duplicate_pending_claim_request_after_force_close_ed278d @@ -0,0 +1 @@ +ssв \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/fc_dust_path_claim_expected_fail_but_sent_5099d3 b/fuzz/test_cases/chanmon_consistency/fc_dust_path_claim_expected_fail_but_sent_5099d3 new file mode 100644 index 00000000000..929b1728ecc --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/fc_dust_path_claim_expected_fail_but_sent_5099d3 @@ -0,0 +1 @@ +a4< \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/fc_dust_path_claim_expected_fail_but_sent_595140 b/fuzz/test_cases/chanmon_consistency/fc_dust_path_claim_expected_fail_but_sent_595140 new file mode 100644 index 00000000000..4f7d29ec00b --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/fc_dust_path_claim_expected_fail_but_sent_595140 @@ -0,0 +1 @@ +== \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/fc_dust_path_claim_expected_fail_but_sent_7a4062 b/fuzz/test_cases/chanmon_consistency/fc_dust_path_claim_expected_fail_but_sent_7a4062 new file mode 100644 index 00000000000..3c111cc19d4 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/fc_dust_path_claim_expected_fail_but_sent_7a4062 @@ -0,0 +1 @@ +#4<< \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/fc_dust_path_claim_expected_fail_but_sent_9d7311 b/fuzz/test_cases/chanmon_consistency/fc_dust_path_claim_expected_fail_but_sent_9d7311 new file mode 100644 index 00000000000..cccf82bbeea --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/fc_dust_path_claim_expected_fail_but_sent_9d7311 @@ -0,0 +1 @@ +<= \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/fc_dust_path_claim_expected_fail_but_sent_b1281e b/fuzz/test_cases/chanmon_consistency/fc_dust_path_claim_expected_fail_but_sent_b1281e new file mode 100644 index 00000000000..7048a0c7729 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/fc_dust_path_claim_expected_fail_but_sent_b1281e @@ -0,0 +1 @@ += \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/fc_dust_path_claim_expected_fail_but_sent_bf210c b/fuzz/test_cases/chanmon_consistency/fc_dust_path_claim_expected_fail_but_sent_bf210c new file mode 100644 index 00000000000..1099dc29283 --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/fc_dust_path_claim_expected_fail_but_sent_bf210c @@ -0,0 +1 @@ +<<<4 \ No newline at end of file diff --git a/fuzz/test_cases/chanmon_consistency/fc_monitor_update_replay_out_of_order_dcbc86 b/fuzz/test_cases/chanmon_consistency/fc_monitor_update_replay_out_of_order_dcbc86 new file mode 100644 index 00000000000..4d9889c435e --- /dev/null +++ b/fuzz/test_cases/chanmon_consistency/fc_monitor_update_replay_out_of_order_dcbc86 @@ -0,0 +1 @@ +P \ No newline at end of file From d57d366983af48a11cfc638f389a00d60766de27 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Sat, 18 Apr 2026 20:39:26 +0200 Subject: [PATCH 11/12] Refactor chanmon monitor snapshot ownership Move monitor snapshot tracking into HarnessPersister. Remove the TestChainMonitor wrapper. Restarts and settle paths now drive the real ChainMonitor directly, while the persister stays the source of truth for in-flight monitor snapshots. Verified with ~/repo/rl-tools/run_fuzz_runner.sh --timeout-secs 20: 383 ok, 5 failed, 0 timed out. The remaining failures are the known contentious-claim and duplicate-claim families. AI tools were used in preparing this commit. --- fuzz/src/chanmon_consistency.rs | 589 ++++++++++++++++---------------- 1 file changed, 304 insertions(+), 285 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 76dacef378b..b000e9eae35 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -42,8 +42,7 @@ use lightning::chain; use lightning::chain::chaininterface::{ BroadcasterInterface, ConfirmationTarget, FeeEstimator, TransactionType, }; -use lightning::chain::channelmonitor::{Balance, ChannelMonitor, MonitorEvent}; -use lightning::chain::transaction::OutPoint; +use lightning::chain::channelmonitor::{Balance, ChannelMonitor}; use lightning::chain::{ chainmonitor, channelmonitor, BestBlock, ChannelMonitorUpdateStatus, Confirm, Watch, }; @@ -314,12 +313,74 @@ struct HarnessPersister { pub update_ret: Mutex, pub latest_monitors: Arc>>, } +impl HarnessPersister { + fn track_monitor_update( + &self, channel_id: ChannelId, monitor_id: u64, serialized_monitor: Vec, + status: chain::ChannelMonitorUpdateStatus, + ) { + let mut latest_monitors = self.latest_monitors.lock().unwrap(); + if let Some(state) = latest_monitors.get_mut(&channel_id) { + match status { + chain::ChannelMonitorUpdateStatus::Completed => { + state.pending_monitors.retain(|(id, _)| *id != monitor_id); + state.persisted_monitor_id = monitor_id; + state.persisted_monitor = serialized_monitor; + }, + chain::ChannelMonitorUpdateStatus::InProgress => { + if let Some((_, pending_monitor)) = + state.pending_monitors.iter_mut().find(|(id, _)| *id == monitor_id) + { + *pending_monitor = serialized_monitor; + } else { + state.pending_monitors.push((monitor_id, serialized_monitor)); + state.pending_monitors.sort_by_key(|(id, _)| *id); + } + }, + chain::ChannelMonitorUpdateStatus::UnrecoverableError => {}, + } + } else { + let state = match status { + chain::ChannelMonitorUpdateStatus::Completed => LatestMonitorState { + persisted_monitor_id: monitor_id, + persisted_monitor: serialized_monitor, + pending_monitors: Vec::new(), + }, + chain::ChannelMonitorUpdateStatus::InProgress => LatestMonitorState { + persisted_monitor_id: monitor_id, + persisted_monitor: Vec::new(), + pending_monitors: vec![(monitor_id, serialized_monitor)], + }, + chain::ChannelMonitorUpdateStatus::UnrecoverableError => return, + }; + assert!( + latest_monitors.insert(channel_id, state).is_none(), + "Already had monitor state pre-persist" + ); + } + } + + fn mark_update_completed( + &self, channel_id: ChannelId, monitor_id: u64, serialized_monitor: Vec, + ) { + if let Some(state) = self.latest_monitors.lock().unwrap().get_mut(&channel_id) { + state.pending_monitors.retain(|(id, _)| *id != monitor_id); + if monitor_id >= state.persisted_monitor_id { + state.persisted_monitor_id = monitor_id; + state.persisted_monitor = serialized_monitor; + } + } + } +} impl chainmonitor::Persist for HarnessPersister { fn persist_new_channel( &self, _monitor_name: lightning::util::persist::MonitorName, - _data: &channelmonitor::ChannelMonitor, + data: &channelmonitor::ChannelMonitor, ) -> chain::ChannelMonitorUpdateStatus { - self.update_ret.lock().unwrap().clone() + let status = self.update_ret.lock().unwrap().clone(); + let monitor_id = data.get_latest_update_id(); + let serialized_monitor = serialize_monitor(data); + self.track_monitor_update(data.channel_id(), monitor_id, serialized_monitor, status); + status } fn update_persisted_channel( @@ -328,157 +389,43 @@ impl chainmonitor::Persist for HarnessPersister { data: &channelmonitor::ChannelMonitor, ) -> chain::ChannelMonitorUpdateStatus { let status = self.update_ret.lock().unwrap().clone(); - if update.is_none() { - if let Some(state) = self.latest_monitors.lock().unwrap().get_mut(&data.channel_id()) { - let monitor_id = data.get_latest_update_id(); - let serialized_monitor = serialize_monitor(data); - match status { - chain::ChannelMonitorUpdateStatus::Completed => { - state.pending_monitors.retain(|(id, _)| *id != monitor_id); - state.persisted_monitor_id = monitor_id; - state.persisted_monitor = serialized_monitor; - }, - chain::ChannelMonitorUpdateStatus::InProgress => { - if let Some((_, pending_monitor)) = - state.pending_monitors.iter_mut().find(|(id, _)| *id == monitor_id) - { - *pending_monitor = serialized_monitor; - } else { - state.pending_monitors.push((monitor_id, serialized_monitor)); - state.pending_monitors.sort_by_key(|(id, _)| *id); - } - }, - chain::ChannelMonitorUpdateStatus::UnrecoverableError => {}, - } - } - } + let monitor_id = update.map_or_else(|| data.get_latest_update_id(), |upd| upd.update_id); + let serialized_monitor = serialize_monitor(data); + self.track_monitor_update(data.channel_id(), monitor_id, serialized_monitor, status); status } fn archive_persisted_channel(&self, _monitor_name: lightning::util::persist::MonitorName) {} } -struct TestChainMonitor { - pub logger: Arc, - pub keys: Arc, - pub persister: Arc, - pub chain_monitor: Arc< - chainmonitor::ChainMonitor< - TestChannelSigner, - Arc, - Arc, - Arc, - Arc, - Arc, - Arc, - >, - >, - pub latest_monitors: Arc>>, -} -impl TestChainMonitor { - pub fn new( - broadcaster: Arc, logger: Arc, feeest: Arc, - initial_update_ret: ChannelMonitorUpdateStatus, keys: Arc, - ) -> Self { - let latest_monitors = Arc::new(Mutex::new(new_hash_map())); - let persister = Arc::new(HarnessPersister { - update_ret: Mutex::new(initial_update_ret), - latest_monitors: Arc::clone(&latest_monitors), - }); - Self { - chain_monitor: Arc::new(chainmonitor::ChainMonitor::new( - None, - broadcaster, - logger.clone(), - feeest, - Arc::clone(&persister), - Arc::clone(&keys), - keys.get_peer_storage_key(), - false, - )), - logger, - keys, - persister, - latest_monitors, - } - } -} -impl chain::Watch for TestChainMonitor { - fn watch_channel( - &self, channel_id: ChannelId, monitor: channelmonitor::ChannelMonitor, - ) -> Result { - let ser = serialize_monitor(&monitor); - let monitor_id = monitor.get_latest_update_id(); - let res = self.chain_monitor.watch_channel(channel_id, monitor); - let state = match res { - Ok(chain::ChannelMonitorUpdateStatus::Completed) => LatestMonitorState { - persisted_monitor_id: monitor_id, - persisted_monitor: ser, - pending_monitors: Vec::new(), - }, - Ok(chain::ChannelMonitorUpdateStatus::InProgress) => LatestMonitorState { - persisted_monitor_id: monitor_id, - persisted_monitor: Vec::new(), - pending_monitors: vec![(monitor_id, ser)], - }, - Ok(chain::ChannelMonitorUpdateStatus::UnrecoverableError) => panic!(), - Err(()) => panic!(), - }; - if self.latest_monitors.lock().unwrap().insert(channel_id, state).is_some() { - panic!("Already had monitor pre-watch_channel"); - } - res - } - - fn update_channel( - &self, channel_id: ChannelId, update: &channelmonitor::ChannelMonitorUpdate, - ) -> chain::ChannelMonitorUpdateStatus { - let mut map_lock = self.latest_monitors.lock().unwrap(); - let map_entry = map_lock.get_mut(&channel_id).expect("Didn't have monitor on update call"); - let latest_monitor_data = match map_entry.pending_monitors.last() { - Some((pending_id, pending_data)) - if *pending_id > map_entry.persisted_monitor_id - || map_entry.persisted_monitor.is_empty() => - { - pending_data - }, - _ => &map_entry.persisted_monitor, - }; - let deserialized_monitor = - <(BestBlock, channelmonitor::ChannelMonitor)>::read( - &mut &latest_monitor_data[..], - (&*self.keys, &*self.keys), - ) - .unwrap() - .1; - deserialized_monitor - .update_monitor( - update, - &&TestBroadcaster { txn_broadcasted: RefCell::new(Vec::new()) }, - &&FuzzEstimator { ret_val: atomic::AtomicU32::new(253) }, - &self.logger, - ) - .unwrap(); - let ser = serialize_monitor(&deserialized_monitor); - let res = self.chain_monitor.update_channel(channel_id, update); - match res { - chain::ChannelMonitorUpdateStatus::Completed => { - map_entry.persisted_monitor_id = update.update_id; - map_entry.persisted_monitor = ser; - }, - chain::ChannelMonitorUpdateStatus::InProgress => { - map_entry.pending_monitors.push((update.update_id, ser)); - }, - chain::ChannelMonitorUpdateStatus::UnrecoverableError => panic!(), - } - res - } +type HarnessChainMonitor = chainmonitor::ChainMonitor< + TestChannelSigner, + Arc, + Arc, + Arc, + Arc, + Arc, + Arc, +>; - fn release_pending_monitor_events( - &self, - ) -> Vec<(OutPoint, ChannelId, Vec, PublicKey)> { - return self.chain_monitor.release_pending_monitor_events(); - } +fn new_chain_monitor( + broadcaster: Arc, logger: Arc, feeest: Arc, + initial_update_ret: ChannelMonitorUpdateStatus, keys: Arc, +) -> (Arc, Arc) { + let latest_monitors = Arc::new(Mutex::new(new_hash_map())); + let persister = + Arc::new(HarnessPersister { update_ret: Mutex::new(initial_update_ret), latest_monitors }); + let chain_monitor = Arc::new(chainmonitor::ChainMonitor::new( + None, + broadcaster, + logger, + feeest, + Arc::clone(&persister), + Arc::clone(&keys), + keys.get_peer_storage_key(), + false, + )); + (chain_monitor, persister) } struct KeyProvider { @@ -668,7 +615,7 @@ fn check_payment_send_events(source: &ChanMan, sent_payment_id: PaymentId) -> bo } type ChanMan<'a> = ChannelManager< - Arc, + Arc, Arc, Arc, Arc, @@ -1058,13 +1005,13 @@ pub fn do_test(data: &[u8], out: Out) { rand_bytes_id: atomic::AtomicU32::new(0), enforcement_states: Mutex::new(new_hash_map()), }); - let monitor = Arc::new(TestChainMonitor::new( + let (monitor, persister) = new_chain_monitor( $broadcaster.clone(), logger.clone(), $fee_estimator.clone(), mon_style[$node_id as usize].borrow().clone(), Arc::clone(&keys_manager), - )); + ); let mut config = UserConfig::default(); config.channel_config.forwarding_fee_proportional_millionths = 0; @@ -1108,6 +1055,7 @@ pub fn do_test(data: &[u8], out: Out) { best_block_timestamp, ), monitor, + persister, keys_manager, logger, ) @@ -1116,7 +1064,7 @@ pub fn do_test(data: &[u8], out: Out) { let reload_node = |ser: &Vec, node_id: u8, - old_monitors: &TestChainMonitor, + old_persister: &Arc, mut use_old_mons: usize, keys, fee_estimator, @@ -1124,13 +1072,13 @@ pub fn do_test(data: &[u8], out: Out) { let keys_manager = Arc::clone(keys); let logger: Arc = Arc::new(test_logger::TestLogger::new(node_id.to_string(), out.clone())); - let chain_monitor = Arc::new(TestChainMonitor::new( + let (chain_monitor, persister) = new_chain_monitor( broadcaster.clone(), logger.clone(), Arc::clone(fee_estimator), ChannelMonitorUpdateStatus::Completed, Arc::clone(keys), - )); + ); let mut config = UserConfig::default(); config.channel_config.forwarding_fee_proportional_millionths = 0; @@ -1157,7 +1105,7 @@ pub fn do_test(data: &[u8], out: Out) { } let mut monitors = new_hash_map(); - let mut old_monitors = old_monitors.latest_monitors.lock().unwrap(); + let mut old_monitors = old_persister.latest_monitors.lock().unwrap(); for (channel_id, mut prev_state) in old_monitors.drain() { let (mon_id, serialized_mon) = if use_old_mons % 3 == 0 { // Reload with the oldest `ChannelMonitor` (the one that we already told @@ -1189,7 +1137,7 @@ pub fn do_test(data: &[u8], out: Out) { // those updates from the `ChannelManager`; if we keep them here we // stop exercising the stale-monitor recovery path entirely. prev_state.pending_monitors.clear(); - chain_monitor.latest_monitors.lock().unwrap().insert(channel_id, prev_state); + persister.latest_monitors.lock().unwrap().insert(channel_id, prev_state); } let mut monitor_refs = new_hash_map(); for (channel_id, monitor) in monitors.iter() { @@ -1212,39 +1160,27 @@ pub fn do_test(data: &[u8], out: Out) { let manager = <(BestBlock, ChanMan)>::read(&mut &ser[..], read_args).expect("Failed to read manager"); - let res = (manager.1, chain_monitor.clone()); + let res = (manager.1, chain_monitor.clone(), persister.clone()); let expected_status = *mon_style[node_id as usize].borrow(); - *chain_monitor.persister.update_ret.lock().unwrap() = expected_status.clone(); + *persister.update_ret.lock().unwrap() = expected_status.clone(); for (channel_id, mon) in monitors.drain() { - let monitor_id = mon.get_latest_update_id(); - assert_eq!( - chain_monitor.chain_monitor.watch_channel(channel_id, mon), - Ok(expected_status.clone()) - ); - // We call the inner `ChainMonitor::watch_channel` directly during - // restart, so mirror its initial in-progress update in - // `latest_monitors` for the harness bookkeeping. - if expected_status == chain::ChannelMonitorUpdateStatus::InProgress { - let mut map = chain_monitor.latest_monitors.lock().unwrap(); - if let Some(state) = map.get_mut(&channel_id) { - state.pending_monitors.push((monitor_id, state.persisted_monitor.clone())); - } - } + assert_eq!(chain_monitor.watch_channel(channel_id, mon), Ok(expected_status.clone())); } res }; macro_rules! complete_all_pending_monitor_updates { - ($monitor: expr) => {{ - for (channel_id, state) in $monitor.latest_monitors.lock().unwrap().iter_mut() { + ($monitor: expr, $persister: expr) => {{ + let mut completed_updates = Vec::new(); + for (channel_id, state) in $persister.latest_monitors.lock().unwrap().iter_mut() { for (id, data) in state.pending_monitors.drain(..) { - $monitor.chain_monitor.channel_monitor_updated(*channel_id, id).unwrap(); - if id >= state.persisted_monitor_id { - state.persisted_monitor_id = id; - state.persisted_monitor = data; - } + completed_updates.push((*channel_id, id, data)); } } + for (channel_id, id, data) in completed_updates { + $monitor.channel_monitor_updated(channel_id, id).unwrap(); + $persister.mark_update_completed(channel_id, id, data); + } }}; } macro_rules! connect_peers { @@ -1264,7 +1200,7 @@ pub fn do_test(data: &[u8], out: Out) { }}; } macro_rules! make_channel { - ($source: expr, $dest: expr, $source_monitor: expr, $dest_monitor: expr, $dest_keys_manager: expr, $chan_id: expr, $trusted_open: expr, $trusted_accept: expr) => {{ + ($source: expr, $dest: expr, $source_monitor: expr, $source_persister: expr, $dest_monitor: expr, $dest_persister: expr, $dest_keys_manager: expr, $chan_id: expr, $trusted_open: expr, $trusted_accept: expr) => {{ let user_config = if $trusted_open { let mut user_config = UserConfig::default(); user_config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = false; @@ -1376,7 +1312,7 @@ pub fn do_test(data: &[u8], out: Out) { } }; $dest.handle_funding_created($source.get_our_node_id(), &funding_created); - complete_all_pending_monitor_updates!($dest_monitor); + complete_all_pending_monitor_updates!($dest_monitor, $dest_persister); let (funding_signed, channel_id) = { let events = $dest.get_and_clear_pending_msg_events(); @@ -1396,7 +1332,7 @@ pub fn do_test(data: &[u8], out: Out) { } $source.handle_funding_signed($dest.get_our_node_id(), &funding_signed); - complete_all_pending_monitor_updates!($source_monitor); + complete_all_pending_monitor_updates!($source_monitor, $source_persister); let events = $source.get_and_clear_pending_events(); assert_eq!(events.len(), 1); @@ -1412,7 +1348,7 @@ pub fn do_test(data: &[u8], out: Out) { panic!("Wrong event type"); } }}; - ($source: expr, $dest: expr, $source_monitor: expr, $dest_monitor: expr, $dest_keys_manager: expr, $chan_id: expr) => {{ + ($source: expr, $dest: expr, $source_monitor: expr, $source_persister: expr, $dest_monitor: expr, $dest_persister: expr, $dest_keys_manager: expr, $chan_id: expr) => {{ $source.create_channel($dest.get_our_node_id(), 100_000, 42, 0, None, None).unwrap(); let open_channel = { let events = $source.get_and_clear_pending_msg_events(); @@ -1502,7 +1438,7 @@ pub fn do_test(data: &[u8], out: Out) { }; $dest.handle_funding_created($source.get_our_node_id(), &funding_created); // Complete any pending monitor updates for dest after watch_channel - complete_all_pending_monitor_updates!($dest_monitor); + complete_all_pending_monitor_updates!($dest_monitor, $dest_persister); let (funding_signed, channel_id) = { let events = $dest.get_and_clear_pending_msg_events(); @@ -1523,7 +1459,7 @@ pub fn do_test(data: &[u8], out: Out) { $source.handle_funding_signed($dest.get_our_node_id(), &funding_signed); // Complete any pending monitor updates for source after watch_channel - complete_all_pending_monitor_updates!($source_monitor); + complete_all_pending_monitor_updates!($source_monitor, $source_persister); let events = $source.get_and_clear_pending_events(); assert_eq!(events.len(), 1); @@ -1646,9 +1582,12 @@ pub fn do_test(data: &[u8], out: Out) { // 3 nodes is enough to hit all the possible cases, notably unknown-source-unknown-dest // forwarding. - let (node_a, mut monitor_a, keys_manager_a, logger_a) = make_node!(0, fee_est_a, broadcast_a); - let (node_b, mut monitor_b, keys_manager_b, logger_b) = make_node!(1, fee_est_b, broadcast_b); - let (node_c, mut monitor_c, keys_manager_c, logger_c) = make_node!(2, fee_est_c, broadcast_c); + let (node_a, mut monitor_a, mut persister_a, keys_manager_a, logger_a) = + make_node!(0, fee_est_a, broadcast_a); + let (node_b, mut monitor_b, mut persister_b, keys_manager_b, logger_b) = + make_node!(1, fee_est_b, broadcast_b); + let (node_c, mut monitor_c, mut persister_c, keys_manager_c, logger_c) = + make_node!(2, fee_est_c, broadcast_c); let mut nodes = [node_a, node_b, node_c]; #[allow(unused_variables)] @@ -1669,14 +1608,80 @@ pub fn do_test(data: &[u8], out: Out) { // B-C with Version(3)). // A-B: channel 2 A and B have 0-reserve (trusted open + trusted accept), // channel 3 A has 0-reserve (trusted accept) - make_channel!(nodes[0], nodes[1], monitor_a, monitor_b, keys_manager_b, 1, false, false); - make_channel!(nodes[0], nodes[1], monitor_a, monitor_b, keys_manager_b, 2, true, true); - make_channel!(nodes[0], nodes[1], monitor_a, monitor_b, keys_manager_b, 3, false, true); + make_channel!( + nodes[0], + nodes[1], + monitor_a, + persister_a, + monitor_b, + persister_b, + keys_manager_b, + 1, + false, + false + ); + make_channel!( + nodes[0], + nodes[1], + monitor_a, + persister_a, + monitor_b, + persister_b, + keys_manager_b, + 2, + true, + true + ); + make_channel!( + nodes[0], + nodes[1], + monitor_a, + persister_a, + monitor_b, + persister_b, + keys_manager_b, + 3, + false, + true + ); // B-C: channel 4 B has 0-reserve (via trusted accept), // channel 5 C has 0-reserve (via trusted open) - make_channel!(nodes[1], nodes[2], monitor_b, monitor_c, keys_manager_c, 4, false, true); - make_channel!(nodes[1], nodes[2], monitor_b, monitor_c, keys_manager_c, 5, true, false); - make_channel!(nodes[1], nodes[2], monitor_b, monitor_c, keys_manager_c, 6, false, false); + make_channel!( + nodes[1], + nodes[2], + monitor_b, + persister_b, + monitor_c, + persister_c, + keys_manager_c, + 4, + false, + true + ); + make_channel!( + nodes[1], + nodes[2], + monitor_b, + persister_b, + monitor_c, + persister_c, + keys_manager_c, + 5, + true, + false + ); + make_channel!( + nodes[1], + nodes[2], + monitor_b, + persister_b, + monitor_c, + persister_c, + keys_manager_c, + 6, + false, + false + ); // Wipe the transactions-broadcasted set to make sure we don't broadcast any transactions // during normal operation in `test_return`. @@ -1686,7 +1691,7 @@ pub fn do_test(data: &[u8], out: Out) { let sync_with_chain_state = |chain_state: &ChainState, node: &ChannelManager<_, _, _, _, _, _, _, _, _>, - monitor: &TestChainMonitor, + monitor: &HarnessChainMonitor, node_height: &mut u32, num_blocks: Option| { let target_height = if let Some(num_blocks) = num_blocks { @@ -1703,24 +1708,24 @@ pub fn do_test(data: &[u8], out: Out) { if next_height > target_height { *node_height = target_height; let (header, _) = chain_state.block_at(*node_height); - monitor.chain_monitor.best_block_updated(header, *node_height); + monitor.best_block_updated(header, *node_height); node.best_block_updated(header, *node_height); break; } if next_height > *node_height + 1 { *node_height = next_height - 1; let (header, _) = chain_state.block_at(*node_height); - monitor.chain_monitor.best_block_updated(header, *node_height); + monitor.best_block_updated(header, *node_height); node.best_block_updated(header, *node_height); } *node_height = next_height; let (header, txn) = chain_state.block_at(*node_height); let txdata: Vec<_> = txn.iter().enumerate().map(|(i, tx)| (i + 1, tx)).collect(); if !txdata.is_empty() { - monitor.chain_monitor.transactions_confirmed(header, &txdata, *node_height); + monitor.transactions_confirmed(header, &txdata, *node_height); node.transactions_confirmed(header, &txdata, *node_height); } - monitor.chain_monitor.best_block_updated(header, *node_height); + monitor.best_block_updated(header, *node_height); node.best_block_updated(header, *node_height); } }; @@ -2633,42 +2638,50 @@ pub fn do_test(data: &[u8], out: Out) { let complete_first = |v: &mut Vec<_>| if !v.is_empty() { Some(v.remove(0)) } else { None }; let complete_second = |v: &mut Vec<_>| if v.len() > 1 { Some(v.remove(1)) } else { None }; let complete_monitor_update = - |monitor: &Arc, + |monitor: &Arc, + persister: &Arc, chan_funding, compl_selector: &dyn Fn(&mut Vec<(u64, Vec)>) -> Option<(u64, Vec)>| { - if let Some(state) = monitor.latest_monitors.lock().unwrap().get_mut(chan_funding) { - assert!( - state.pending_monitors.windows(2).all(|pair| pair[0].0 < pair[1].0), - "updates should be sorted by id" - ); - if let Some((id, data)) = compl_selector(&mut state.pending_monitors) { - monitor.chain_monitor.channel_monitor_updated(*chan_funding, id).unwrap(); - if id >= state.persisted_monitor_id { - state.persisted_monitor_id = id; - state.persisted_monitor = data; - } + let completed_update = { + let mut latest_monitors = persister.latest_monitors.lock().unwrap(); + if let Some(state) = latest_monitors.get_mut(chan_funding) { + assert!( + state.pending_monitors.windows(2).all(|pair| pair[0].0 < pair[1].0), + "updates should be sorted by id" + ); + compl_selector(&mut state.pending_monitors) + } else { + None } + }; + if let Some((id, data)) = completed_update { + monitor.channel_monitor_updated(*chan_funding, id).unwrap(); + persister.mark_update_completed(*chan_funding, id, data); } }; - let complete_all_monitor_updates = |monitor: &Arc, chan_id| { - let mut completed_any = false; - if let Some(state) = monitor.latest_monitors.lock().unwrap().get_mut(chan_id) { - assert!( - state.pending_monitors.windows(2).all(|pair| pair[0].0 < pair[1].0), - "updates should be sorted by id" - ); - for (id, data) in state.pending_monitors.drain(..) { - completed_any = true; - monitor.chain_monitor.channel_monitor_updated(*chan_id, id).unwrap(); - if id >= state.persisted_monitor_id { - state.persisted_monitor_id = id; - state.persisted_monitor = data; + let complete_all_monitor_updates = + |monitor: &Arc, persister: &Arc, chan_id| { + let completed_updates = { + let mut latest_monitors = persister.latest_monitors.lock().unwrap(); + if let Some(state) = latest_monitors.get_mut(chan_id) { + assert!( + state.pending_monitors.windows(2).all(|pair| pair[0].0 < pair[1].0), + "updates should be sorted by id" + ); + state.pending_monitors.drain(..).collect::>() + } else { + Vec::new() } + }; + let mut completed_any = false; + for (id, data) in completed_updates { + completed_any = true; + monitor.channel_monitor_updated(*chan_id, id).unwrap(); + persister.mark_update_completed(*chan_id, id, data); } - } - completed_any - }; + completed_any + }; let send = |source_idx: usize, dest_idx: usize, dest_chan_id, amt, payment_ctr: &mut u64| { @@ -2837,8 +2850,8 @@ pub fn do_test(data: &[u8], out: Out) { macro_rules! has_pending_monitor_updates { () => {{ - [&monitor_a, &monitor_b, &monitor_c].iter().any(|monitor| { - monitor + [&persister_a, &persister_b, &persister_c].iter().any(|persister| { + persister .latest_monitors .lock() .unwrap() @@ -2858,7 +2871,7 @@ pub fn do_test(data: &[u8], out: Out) { .collect::>(); let open_refs: Vec<&_> = open_channels.iter().collect(); [&monitor_a, &monitor_b, &monitor_c].iter().any(|monitor| { - monitor.chain_monitor.get_claimable_balances(&open_refs).iter().any(|balance| { + monitor.get_claimable_balances(&open_refs).iter().any(|balance| { matches!( balance, Balance::ClaimableOnChannelClose { .. } @@ -2895,9 +2908,9 @@ pub fn do_test(data: &[u8], out: Out) { .cloned() .collect::>(); let open_refs: Vec<&_> = open_channels.iter().collect(); - let balances_a = monitor_a.chain_monitor.get_claimable_balances(&open_refs); - let balances_b = monitor_b.chain_monitor.get_claimable_balances(&open_refs); - let balances_c = monitor_c.chain_monitor.get_claimable_balances(&open_refs); + let balances_a = monitor_a.get_claimable_balances(&open_refs); + let balances_b = monitor_b.get_claimable_balances(&open_refs); + let balances_c = monitor_c.get_claimable_balances(&open_refs); format!( "queues ab={} ba={} bc={} cb={} bcast=({},{},{}) pending=({},{},{}) monitor_updates={} timed_work={} heights=({},{},{}) tip={} {} balances_a=[{}] balances_b=[{}] balances_c=[{}]", ab_events.len(), @@ -2928,12 +2941,16 @@ pub fn do_test(data: &[u8], out: Out) { () => {{ let mut completed_monitor_update = false; for id in &chan_ab_ids { - completed_monitor_update |= complete_all_monitor_updates(&monitor_a, id); - completed_monitor_update |= complete_all_monitor_updates(&monitor_b, id); + completed_monitor_update |= + complete_all_monitor_updates(&monitor_a, &persister_a, id); + completed_monitor_update |= + complete_all_monitor_updates(&monitor_b, &persister_b, id); } for id in &chan_bc_ids { - completed_monitor_update |= complete_all_monitor_updates(&monitor_b, id); - completed_monitor_update |= complete_all_monitor_updates(&monitor_c, id); + completed_monitor_update |= + complete_all_monitor_updates(&monitor_b, &persister_b, id); + completed_monitor_update |= + complete_all_monitor_updates(&monitor_c, &persister_c, id); } let mut had_msg_or_ev = false; if process_msg_events!(0, false, ProcessMessages::AllMessages) { @@ -2990,7 +3007,7 @@ pub fn do_test(data: &[u8], out: Out) { Arc::clone(&loggers[idx]), ); let broadcaster = broadcasters[idx]; - monitor.chain_monitor.process_pending_events(&|event: events::Event| { + monitor.process_pending_events(&|event: events::Event| { if let events::Event::BumpTransaction(ref bump) = event { match bump { // Commitment transactions are already @@ -3189,22 +3206,22 @@ pub fn do_test(data: &[u8], out: Out) { 0x08 => { for id in &chan_ab_ids { - complete_all_monitor_updates(&monitor_a, id); + complete_all_monitor_updates(&monitor_a, &persister_a, id); } }, 0x09 => { for id in &chan_ab_ids { - complete_all_monitor_updates(&monitor_b, id); + complete_all_monitor_updates(&monitor_b, &persister_b, id); } }, 0x0a => { for id in &chan_bc_ids { - complete_all_monitor_updates(&monitor_b, id); + complete_all_monitor_updates(&monitor_b, &persister_b, id); } }, 0x0b => { for id in &chan_bc_ids { - complete_all_monitor_updates(&monitor_c, id); + complete_all_monitor_updates(&monitor_c, &persister_c, id); } }, @@ -3541,10 +3558,10 @@ pub fn do_test(data: &[u8], out: Out) { ab_events.clear(); ba_events.clear(); } - let (new_node_a, new_monitor_a) = reload_node( + let (new_node_a, new_monitor_a, new_persister_a) = reload_node( &node_a_ser, 0, - &monitor_a, + &persister_a, v as usize, &keys_manager_a, &fee_est_a, @@ -3552,6 +3569,7 @@ pub fn do_test(data: &[u8], out: Out) { ); nodes[0] = new_node_a; monitor_a = new_monitor_a; + persister_a = new_persister_a; }, 0xb3..=0xbb => { // Restart node B, picking among the in-flight `ChannelMonitor`s to use based on @@ -3570,10 +3588,10 @@ pub fn do_test(data: &[u8], out: Out) { bc_events.clear(); cb_events.clear(); } - let (new_node_b, new_monitor_b) = reload_node( + let (new_node_b, new_monitor_b, new_persister_b) = reload_node( &node_b_ser, 1, - &monitor_b, + &persister_b, v as usize, &keys_manager_b, &fee_est_b, @@ -3581,6 +3599,7 @@ pub fn do_test(data: &[u8], out: Out) { ); nodes[1] = new_node_b; monitor_b = new_monitor_b; + persister_b = new_persister_b; }, 0xbc | 0xbd | 0xbe => { // Restart node C, picking among the in-flight `ChannelMonitor`s to use based on @@ -3595,10 +3614,10 @@ pub fn do_test(data: &[u8], out: Out) { bc_events.clear(); cb_events.clear(); } - let (new_node_c, new_monitor_c) = reload_node( + let (new_node_c, new_monitor_c, new_persister_c) = reload_node( &node_c_ser, 2, - &monitor_c, + &persister_c, v as usize, &keys_manager_c, &fee_est_c, @@ -3606,6 +3625,7 @@ pub fn do_test(data: &[u8], out: Out) { ); nodes[2] = new_node_c; monitor_c = new_monitor_c; + persister_c = new_persister_c; }, 0xc0 => keys_manager_a.disable_supported_ops_for_all_signers(), @@ -3770,65 +3790,65 @@ pub fn do_test(data: &[u8], out: Out) { 0xf0 => { for id in &chan_ab_ids { - complete_monitor_update(&monitor_a, id, &complete_first); + complete_monitor_update(&monitor_a, &persister_a, id, &complete_first); } }, 0xf1 => { for id in &chan_ab_ids { - complete_monitor_update(&monitor_a, id, &complete_second); + complete_monitor_update(&monitor_a, &persister_a, id, &complete_second); } }, 0xf2 => { for id in &chan_ab_ids { - complete_monitor_update(&monitor_a, id, &Vec::pop); + complete_monitor_update(&monitor_a, &persister_a, id, &Vec::pop); } }, 0xf4 => { for id in &chan_ab_ids { - complete_monitor_update(&monitor_b, id, &complete_first); + complete_monitor_update(&monitor_b, &persister_b, id, &complete_first); } }, 0xf5 => { for id in &chan_ab_ids { - complete_monitor_update(&monitor_b, id, &complete_second); + complete_monitor_update(&monitor_b, &persister_b, id, &complete_second); } }, 0xf6 => { for id in &chan_ab_ids { - complete_monitor_update(&monitor_b, id, &Vec::pop); + complete_monitor_update(&monitor_b, &persister_b, id, &Vec::pop); } }, 0xf8 => { for id in &chan_bc_ids { - complete_monitor_update(&monitor_b, id, &complete_first); + complete_monitor_update(&monitor_b, &persister_b, id, &complete_first); } }, 0xf9 => { for id in &chan_bc_ids { - complete_monitor_update(&monitor_b, id, &complete_second); + complete_monitor_update(&monitor_b, &persister_b, id, &complete_second); } }, 0xfa => { for id in &chan_bc_ids { - complete_monitor_update(&monitor_b, id, &Vec::pop); + complete_monitor_update(&monitor_b, &persister_b, id, &Vec::pop); } }, 0xfc => { for id in &chan_bc_ids { - complete_monitor_update(&monitor_c, id, &complete_first); + complete_monitor_update(&monitor_c, &persister_c, id, &complete_first); } }, 0xfd => { for id in &chan_bc_ids { - complete_monitor_update(&monitor_c, id, &complete_second); + complete_monitor_update(&monitor_c, &persister_c, id, &complete_second); } }, 0xfe => { for id in &chan_bc_ids { - complete_monitor_update(&monitor_c, id, &Vec::pop); + complete_monitor_update(&monitor_c, &persister_c, id, &Vec::pop); } }, @@ -3885,15 +3905,15 @@ pub fn do_test(data: &[u8], out: Out) { let mut completed_monitor_update = false; for id in &chan_ab_ids { completed_monitor_update |= - complete_all_monitor_updates(&monitor_a, id); + complete_all_monitor_updates(&monitor_a, &persister_a, id); completed_monitor_update |= - complete_all_monitor_updates(&monitor_b, id); + complete_all_monitor_updates(&monitor_b, &persister_b, id); } for id in &chan_bc_ids { completed_monitor_update |= - complete_all_monitor_updates(&monitor_b, id); + complete_all_monitor_updates(&monitor_b, &persister_b, id); completed_monitor_update |= - complete_all_monitor_updates(&monitor_c, id); + complete_all_monitor_updates(&monitor_c, &persister_c, id); } let mut had_msg_or_ev = false; @@ -3951,8 +3971,8 @@ pub fn do_test(data: &[u8], out: Out) { (&monitor_c, &node_height_c), ] { let mut min_monitor_height = *node_height; - for chan_id in monitor.chain_monitor.list_monitors() { - if let Ok(mon) = monitor.chain_monitor.get_monitor(chan_id) { + for chan_id in monitor.list_monitors() { + if let Ok(mon) = monitor.get_monitor(chan_id) { min_monitor_height = std::cmp::min( min_monitor_height, mon.current_best_block().height, @@ -3970,24 +3990,22 @@ pub fn do_test(data: &[u8], out: Out) { if next_height > *node_height { h = *node_height; let (header, _) = chain_state.block_at(h); - monitor.chain_monitor.best_block_updated(header, h); + monitor.best_block_updated(header, h); break; } if next_height > h + 1 { h = next_height - 1; let (header, _) = chain_state.block_at(h); - monitor.chain_monitor.best_block_updated(header, h); + monitor.best_block_updated(header, h); } h = next_height; let (header, txn) = chain_state.block_at(h); let txdata: Vec<_> = txn.iter().enumerate().map(|(i, tx)| (i + 1, tx)).collect(); if !txdata.is_empty() { - monitor - .chain_monitor - .transactions_confirmed(header, &txdata, h); + monitor.transactions_confirmed(header, &txdata, h); } - monitor.chain_monitor.best_block_updated(header, h); + monitor.best_block_updated(header, h); } } }}; @@ -4004,12 +4022,16 @@ pub fn do_test(data: &[u8], out: Out) { // all three because each step may unlock the next. let mut completed_monitor_update = false; for id in &chan_ab_ids { - completed_monitor_update |= complete_all_monitor_updates(&monitor_a, id); - completed_monitor_update |= complete_all_monitor_updates(&monitor_b, id); + completed_monitor_update |= + complete_all_monitor_updates(&monitor_a, &persister_a, id); + completed_monitor_update |= + complete_all_monitor_updates(&monitor_b, &persister_b, id); } for id in &chan_bc_ids { - completed_monitor_update |= complete_all_monitor_updates(&monitor_b, id); - completed_monitor_update |= complete_all_monitor_updates(&monitor_c, id); + completed_monitor_update |= + complete_all_monitor_updates(&monitor_b, &persister_b, id); + completed_monitor_update |= + complete_all_monitor_updates(&monitor_c, &persister_c, id); } // Process messages and events first so nodes // learn preimages before on-chain txs are @@ -4068,8 +4090,7 @@ pub fn do_test(data: &[u8], out: Out) { Arc::clone(&loggers[idx]), ); let broadcaster = broadcasters[idx]; - monitor.chain_monitor.process_pending_events( - &|event: events::Event| { + monitor.process_pending_events(&|event: events::Event| { if let events::Event::BumpTransaction(ref bump) = event { match bump { events::bump_transaction::BumpTransactionEvent::ChannelClose { @@ -4092,8 +4113,7 @@ pub fn do_test(data: &[u8], out: Out) { } } Ok(()) - }, - ); + }); } } let mut had_new_txs = false; @@ -4197,9 +4217,8 @@ pub fn do_test(data: &[u8], out: Out) { ] .into_iter() .any(|(monitor, node_height)| { - monitor.chain_monitor.list_monitors().into_iter().any(|chan_id| { + monitor.list_monitors().into_iter().any(|chan_id| { monitor - .chain_monitor .get_monitor(chan_id) .map(|mon| mon.current_best_block().height < node_height) .unwrap_or(false) @@ -4246,9 +4265,9 @@ pub fn do_test(data: &[u8], out: Out) { .cloned() .collect::>(); let open_refs: Vec<&_> = open_channels.iter().collect(); - let balances_a = monitor_a.chain_monitor.get_claimable_balances(&open_refs); - let balances_b = monitor_b.chain_monitor.get_claimable_balances(&open_refs); - let balances_c = monitor_c.chain_monitor.get_claimable_balances(&open_refs); + let balances_a = monitor_a.get_claimable_balances(&open_refs); + let balances_b = monitor_b.get_claimable_balances(&open_refs); + let balances_c = monitor_c.get_claimable_balances(&open_refs); let needs_payment_completion = has_live_payment_work(); let has_cleanup_balances = !balances_a.is_empty() || !balances_b.is_empty() || !balances_c From 75232fa15150658479b3aaa8eb534c67ace003cf Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Sat, 18 Apr 2026 21:06:42 +0200 Subject: [PATCH 12/12] Fix OnchainTxHandler claim replay bugs Prevent duplicate delayed-claim replays from registering the same ClaimId, and remember irreversibly spent outpoints so later preimages cannot resurrect impossible claims. Update the force-close issue notes and add a detailed OnchainTxHandler bug writeup with raw fuzz repro bytes and verification runs. Verified with ~/repo/rl-tools/run_fuzz_runner.sh --timeout-secs 20: - targeted duplicate-family rerun: 2 ok, 0 failed, 0 timed out - targeted contentious-family rerun: 3 ok, 0 failed, 0 timed out - full corpus rerun: 388 ok, 0 failed, 0 timed out AI tools were used in preparing this commit. --- fuzz/ONCHAINTX-BUGS.md | 232 +++++++++++++++++++++++++++++++ fuzz/OPEN-ISSUES.md | 117 +++------------- lightning/src/chain/onchaintx.rs | 46 +++++- 3 files changed, 290 insertions(+), 105 deletions(-) create mode 100644 fuzz/ONCHAINTX-BUGS.md diff --git a/fuzz/ONCHAINTX-BUGS.md b/fuzz/ONCHAINTX-BUGS.md new file mode 100644 index 00000000000..829b54a6f36 --- /dev/null +++ b/fuzz/ONCHAINTX-BUGS.md @@ -0,0 +1,232 @@ +# Recent `OnchainTxHandler` Bugs And Fixes + +This note records the two `OnchainTxHandler` bugs that were fixed while +hardening the `chanmon_consistency` force-close corpus. + +Both bugs lived in `lightning/src/chain/onchaintx.rs`. Both were real +logic issues, not harness-only artifacts. Both now pass in targeted +reruns and in the full `chanmon_consistency` corpus sweep. + +Current green reference runs: + +- Targeted duplicate-claim rerun: + `fuzz/artifacts/chanmon_runner/run-1776537725/summary.txt` +- Targeted contentious-claim rerun: + `fuzz/artifacts/chanmon_runner/run-1776538115/summary.txt` +- Full corpus rerun: + `fuzz/artifacts/chanmon_runner/run-1776538385/summary.txt` + +Full-corpus result: + +- `388 ok / 0 failed / 0 timed_out / 0 spawn_errors` + +## 1. Duplicate pending claim request after force-close + +### Repro cases + +- `fc_duplicate_pending_claim_request_after_force_close_39b47f` + - bytes: `0fd37373d0b2ffd3` +- `fc_duplicate_pending_claim_request_after_force_close_ed278d` + - bytes: `08d37373d0b2ffd3` + +### What went wrong + +The failing shape was: + +1. A force-close created two single-outpoint claim requests. +2. Those requests were merged into one delayed package because their + timelock was still in the future. +3. A later replay of `update_claims_view_from_requests` at the same + logical state recreated the same two single-outpoint requests. +4. The old dedupe logic only rejected a duplicate delayed claim if the + outpoint sets were exactly equal. +5. Because the existing delayed claim had already been merged into a + two-outpoint package, the new single-outpoint requests were not seen + as duplicates. +6. At the timelock height, the same aggregated delayed package was + restored twice and tried to register the same `ClaimId` twice. + +The crash was the debug assertion in `OnchainTxHandler`: + +- `assertion failed: self.pending_claim_requests.get(&claim_id).is_none()` + +Representative evidence from +`fuzz/artifacts/chanmon_runner/run-1776537612/logs/fc_duplicate_pending_claim_request_after_force_close_ed278d.log`: + +- line `1829`: `Updating claims view at height 61 with 2 claim requests` +- line `1830`: delayed until timelock `361` +- line `2077`: the same `2 claim requests` appear again +- line `17163`: delayed package restored at timelock `361` +- lines `17164` and `17167`: the same two-outpoint event is yielded twice +- line `17169`: assertion failure + +The same pattern appears in the sibling repro +`fc_duplicate_pending_claim_request_after_force_close_39b47f`. + +### Why the old logic was wrong + +Before the fix, delayed-claim dedupe effectively asked: + +- "Do I already have a delayed package with exactly the same outpoint + set as this new request?" + +That was too strict. Once two single-outpoint requests had already been +merged into one delayed package, replaying either single-outpoint +request should have been considered duplicate as well. + +The correct question is: + +- "Is every outpoint in this new request already covered by an existing + delayed package?" + +### The fix + +In `OnchainTxHandler::update_claims_view_from_requests`, the delayed +claim dedupe was changed from exact package equality to covering-package +detection. + +Relevant code: + +- `lightning/src/chain/onchaintx.rs`, `timelocked_covering_package` +- log line for this path: + `Ignoring second claim for outpoint ..., we already have one which + we're waiting on a timelock at ...` + +In practical terms: + +- a fresh single-outpoint request is now ignored if a delayed package + already contains that outpoint +- replaying the same logical claim state no longer creates duplicate + delayed packages +- the delayed package is restored only once at the timelock height + +### Why this fix is correct + +This does not suppress any legitimate new claim. It only rejects a +request whose entire outpoint set is already represented in pending +delayed state. If a request introduces a truly new outpoint, it still +passes through. + +### Verification + +Targeted rerun: + +- `fuzz/artifacts/chanmon_runner/run-1776537725/summary.txt` +- result: `2 ok / 0 failed / 0 timed_out` + +## 2. Contentious claim reused an already resolved outpoint + +### Repro cases + +- `fc_contentious_claim_stuck_after_force_close_218996` + - bytes: `89ffde3d3dc0d3ff` +- `fc_contentious_claim_stuck_after_force_close_36a22e` + - bytes: `2cffde3d3dc0d3ff` +- `fc_contentious_claim_stuck_after_force_close_d7793e` + - bytes: `76ffde3d3dc0d1ff` + +### What went wrong + +The failing shape was: + +1. An HTLC output was claimed on-chain by a single-outpoint claim. +2. That claim matured past `ANTI_REORG_DELAY`. +3. `OnchainTxHandler` removed the pending claim tracking for that + outpoint. +4. A later preimage update arrived and built a fresh two-outpoint claim + that included the already-resolved outpoint again. +5. That new claim could never confirm, because one of its inputs had + already been definitively spent. +6. The handler kept RBF-bumping that impossible claim forever, leaving a + claimed payment stuck pending in the harness. + +Representative evidence from +`fuzz/artifacts/chanmon_runner/run-1776537816/logs/fc_contentious_claim_stuck_after_force_close_d7793e.log`: + +- line `3173`: `Updating claims view at height 60 with 1 claim requests` +- line `3175`: registers claim for + `cc0e...:2` +- line `3282`: removes tracking for `cc0e...:2` after the claim package + matured +- line `3424`: `Updating claims view at height 66 with 2 claim requests` +- line `3425`: yields a new event spending + `cc0e...:1` and `cc0e...:2` +- lines `3426` and `3427`: registers both outpoints again +- lines `4438`, `5380`, `6322`, and many later lines: keeps yielding + RBF-bumped events for that same impossible two-input claim +- line `21640`: final harness failure, + `Node 2 has 1 stuck pending payments after settling all state` + +The same family reproduced in the other two named cases. + +### Why the old logic was wrong + +Removing an outpoint from `claimable_outpoints` after its claim matured +was not enough. That only said: + +- "we no longer need to actively track this pending request" + +It did not preserve the stronger fact: + +- "this outpoint is definitively spent and must never be re-claimed" + +Without that second fact, a later preimage could cause +`update_claims_view_from_requests` to resurrect an already-resolved +outpoint into a new claim package. + +### The fix + +`OnchainTxHandler` now maintains a restart-safe +`irrevocably_spent_outpoints: HashSet`. + +Relevant code paths: + +- field definition: + `lightning/src/chain/onchaintx.rs` +- serialization and deserialization: + the new optional TLV field in `write` and `read` +- request filtering: + `Ignoring claim for outpoint ..., it was already irrevocably spent by + a confirmed claim transaction` +- maturation handling: + outpoints are inserted into `irrevocably_spent_outpoints` when a claim + or contentious outpoint reaches the anti-reorg threshold + +This matters for restarts as well. The spent-outpoint memory is part of +the serialized `OnchainTxHandler` state, so a monitor reload does not +forget that the output was already definitively resolved. + +### Why this fix is correct + +Once a claim tx for an outpoint has reached `ANTI_REORG_DELAY`, the +handler should never generate a new claim for that same outpoint unless +the chain reorgs deep enough to invalidate the confirmation. That is +exactly the invariant the new set captures. + +The fix is intentionally narrow: + +- it does not suppress still-live outpoints +- it does not interfere with normal package splitting or merging +- it only blocks claim generation for outpoints that were already + irreversibly resolved + +### Verification + +Targeted rerun: + +- `fuzz/artifacts/chanmon_runner/run-1776538115/summary.txt` +- result: `3 ok / 0 failed / 0 timed_out` + +## Final verification + +After both fixes landed, the default corpus sweep passed: + +- `fuzz/artifacts/chanmon_runner/run-1776538385/summary.txt` +- result: `388 ok / 0 failed / 0 timed_out / 0 spawn_errors` + +This is the reference run showing that: + +- the duplicate delayed-claim family is fixed +- the contentious reused-outpoint family is fixed +- neither change regressed the previously fixed dust, restart, or + sender-terminal-event invariants diff --git a/fuzz/OPEN-ISSUES.md b/fuzz/OPEN-ISSUES.md index af4eafb8774..a95cb639253 100644 --- a/fuzz/OPEN-ISSUES.md +++ b/fuzz/OPEN-ISSUES.md @@ -1,106 +1,25 @@ # Open Issues -This file tracks the currently failing `chanmon_consistency` cases after the -latest focused dust-invariant fix and targeted reruns. +There are no currently open `chanmon_consistency` issues in this branch. -Current reference run: -`fuzz/artifacts/chanmon_runner/run-1776524663/summary.txt` +Latest verification: -Current renamed failing cases: +- Targeted contentious-family rerun: + `fuzz/artifacts/chanmon_runner/run-1776538115/summary.txt` + with `3 ok / 0 failed / 0 timed_out` +- Full corpus rerun: + `fuzz/artifacts/chanmon_runner/run-1776538385/summary.txt` + with `388 ok / 0 failed / 0 timed_out / 0 spawn_errors` -- `fc_contentious_claim_stuck_after_force_close_218996` -- `fc_contentious_claim_stuck_after_force_close_36a22e` -- `fc_contentious_claim_stuck_after_force_close_d7793e` -- `fc_duplicate_pending_claim_request_after_force_close_39b47f` -- `fc_duplicate_pending_claim_request_after_force_close_ed278d` +Recently resolved: -## Resolved Recently +- Duplicate pending claim request after force-close. + Fixed in `lightning/src/chain/onchaintx.rs` by treating claim requests + as duplicates when their outpoints are already covered by a delayed + package. -- Dust-path sender-failure prediction was too strong. - The targeted rerun - `fuzz/artifacts/chanmon_runner/run-1776524390/summary.txt` - is now clean for: - `fc_claimed_dust_htlc_sender_fails`, - `fc_claimed_mpp_dust_path_still_succeeds`, - and the six `fc_dust_path_claim_expected_fail_but_sent_*` cases. - The harness now treats force-close dust on a used path as making - `PaymentFailed` permissible, not mandatory. - -- Live monitor reconstruction no longer replays updates out of order. - `fc_monitor_update_replay_out_of_order_dcbc86` now passes in - `fuzz/artifacts/chanmon_runner/run-1776524663/summary.txt`. - -## 1. Duplicate pending claim request after force-close - -Cases: - -- `fc_duplicate_pending_claim_request_after_force_close_39b47f` -- `fc_duplicate_pending_claim_request_after_force_close_ed278d` - -Current behavior: - -- The same delayed claim package is yielded twice, then - `lightning/src/chain/onchaintx.rs:913` trips while registering the - duplicate claim request. - -Why this may still be harness fallout: - -- These cases also involve restart and in-flight monitor snapshot - handling. -- If the harness rebuilds the live monitor from stale state, it can - plausibly re-yield the same pending on-chain claim event. - -Relevant evidence: - -- In the representative log - `fuzz/artifacts/chanmon_runner/run-1776524663/logs/fc_duplicate_pending_claim_request_after_force_close_39b47f.log`, - the same pair of outpoints is yielded twice at the same height before - the panic. - -Current conclusion: - -- These survive the live-monitor reconstruction fix. -- The duplicate claim request is now a real remaining bug candidate, - likely in or around `OnchainTxHandler` delayed-claim replay. - -## 2. Claimed payment can get stuck behind a contentious later claim tx - -Cases: - -- `fc_contentious_claim_stuck_after_force_close_218996` -- `fc_contentious_claim_stuck_after_force_close_36a22e` -- `fc_contentious_claim_stuck_after_force_close_d7793e` - -Current behavior: - -- The final harness settle phase ends with a claimed payment still - pending. -- The sender never reaches `PaymentSent`. -- The closing node still reports a `contentious` balance, and the next - hop still reports `maybe_timeout`. - -Why this may be a real LDK bug: - -- In the representative log - `fuzz/artifacts/chanmon_runner/run-1776524663/logs/fc_contentious_claim_stuck_after_force_close_218996.log`, - node `1` first confirms a single-input HTLC claim transaction for one - output, then later builds a second HTLC claim transaction that - includes that already-spent output again together with another HTLC - output. -- Once the earlier tx has confirmed, the later two-input tx is no - longer confirmable. -- The payment then remains stuck in the exact state the hard invariant - is supposed to rule out. - -Current conclusion: - -- This is the strongest remaining candidate for a real LDK bug. -- It still reproduces after the live-monitor reconstruction fix, so the - stale-monitor explanation is no longer enough. - -## Next steps - -1. Investigate why delayed claim packages are yielded twice in the two - `fc_duplicate_pending_claim_request_after_force_close_*` cases. -2. Reduce the three `fc_contentious_claim_stuck_after_force_close_*` - cases to a minimal repro showing the later unconfirmable batch claim. +- Claimed payment stuck after force-close because a later claim reused an + already resolved outpoint. + Fixed in `lightning/src/chain/onchaintx.rs` by tracking HTLC outputs + whose claim tx reached `ANTI_REORG_DELAY` and rejecting later claim + requests for those outpoints, even if a new preimage arrives later. diff --git a/lightning/src/chain/onchaintx.rs b/lightning/src/chain/onchaintx.rs index 3eb6d64f3a2..a4ce9cf50b9 100644 --- a/lightning/src/chain/onchaintx.rs +++ b/lightning/src/chain/onchaintx.rs @@ -273,6 +273,11 @@ pub struct OnchainTxHandler { #[cfg(not(any(test, feature = "_test_utils")))] claimable_outpoints: HashMap, + // Tracks outpoints whose claim tx has already reached [`ANTI_REORG_DELAY`] confirmations. + // Later claim requests for these outputs must be ignored, even if they arrive from newly + // learned preimages after the original claim tracking has been cleaned up. + irrevocably_spent_outpoints: HashSet, + #[cfg(any(test, feature = "_test_utils"))] pub(crate) locktimed_packages: BTreeMap>, #[cfg(not(any(test, feature = "_test_utils")))] @@ -297,6 +302,7 @@ impl PartialEq for OnchainTxHandler OnchainTxHandler { entry.write(writer)?; } - write_tlv_fields!(writer, {}); + let irrevocably_spent_outpoints = Some(self.irrevocably_spent_outpoints.clone()); + write_tlv_fields!(writer, { + (0, irrevocably_spent_outpoints, option), + }); Ok(()) } @@ -441,7 +450,10 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP } } - read_tlv_fields!(reader, {}); + let mut irrevocably_spent_outpoints = None; + read_tlv_fields!(reader, { + (0, irrevocably_spent_outpoints, option), + }); // `ChannelMonitor`s already track the `channel_id` and `counterparty_node_id`, however, due // to the deserialization order there we can't make use of `ReadableArgs` to hand them in @@ -465,6 +477,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP signer, channel_transaction_parameters: channel_parameters, claimable_outpoints, + irrevocably_spent_outpoints: irrevocably_spent_outpoints.unwrap_or_else(new_hash_set), locktimed_packages, pending_claim_requests, onchain_events_awaiting_threshold_conf, @@ -493,6 +506,7 @@ impl OnchainTxHandler { channel_transaction_parameters: channel_parameters, pending_claim_requests: new_hash_map(), claimable_outpoints: new_hash_map(), + irrevocably_spent_outpoints: new_hash_set(), locktimed_packages: BTreeMap::new(), onchain_events_awaiting_threshold_conf: Vec::new(), pending_claim_events: Vec::new(), @@ -806,6 +820,14 @@ impl OnchainTxHandler { 1, "Claims passed to `update_claims_view_from_requests` should not be aggregated" ); + if req.outpoints() + .iter() + .any(|outpoint| self.irrevocably_spent_outpoints.contains(*outpoint)) + { + log_info!(logger, "Ignoring claim for outpoint {}:{}, it was already irrevocably spent by a confirmed claim transaction", + req.outpoints()[0].txid, req.outpoints()[0].vout); + false + } else { let mut all_outpoints_claiming = true; for outpoint in req.outpoints() { if self.claimable_outpoints.get(outpoint).is_none() { @@ -817,9 +839,16 @@ impl OnchainTxHandler { req.outpoints()[0].txid, req.outpoints()[0].vout); false } else { - let timelocked_equivalent_package = self.locktimed_packages.iter().map(|v| v.1.iter()).flatten() - .find(|locked_package| locked_package.outpoints() == req.outpoints()); - if let Some(package) = timelocked_equivalent_package { + let timelocked_covering_package = self + .locktimed_packages + .values() + .flat_map(|packages| packages.iter()) + .find(|locked_package| { + req.outpoints().iter().all(|outpoint| { + locked_package.outpoints().contains(outpoint) + }) + }); + if let Some(package) = timelocked_covering_package { log_info!(logger, "Ignoring second claim for outpoint {}:{}, we already have one which we're waiting on a timelock at {} for.", req.outpoints()[0].txid, req.outpoints()[0].vout, package.package_locktime(cur_height)); false @@ -827,6 +856,7 @@ impl OnchainTxHandler { true } } + } }); // Then try to maximally aggregate `requests`. @@ -1064,6 +1094,7 @@ impl OnchainTxHandler { for outpoint in request.outpoints() { log_debug!(logger, "Removing claim tracking for {} due to maturation of claim package {}.", outpoint, log_bytes!(claim_id.0)); + self.irrevocably_spent_outpoints.insert(*outpoint); self.claimable_outpoints.remove(outpoint); } #[cfg(debug_assertions)] { @@ -1077,7 +1108,10 @@ impl OnchainTxHandler { OnchainEvent::ContentiousOutpoint { package } => { log_debug!(logger, "Removing claim tracking due to maturation of claim tx for outpoints:"); log_debug!(logger, " {:?}", package.outpoints()); - self.claimable_outpoints.remove(package.outpoints()[0]); + for outpoint in package.outpoints() { + self.irrevocably_spent_outpoints.insert(*outpoint); + self.claimable_outpoints.remove(outpoint); + } } } } else {