Make fuzz targets deterministic#4525
Conversation
|
👋 Thanks for assigning @TheBlueMatt as a reviewer! |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #4525 +/- ##
==========================================
- Coverage 87.10% 87.10% -0.01%
==========================================
Files 163 163
Lines 108740 108748 +8
Branches 108740 108748 +8
==========================================
+ Hits 94723 94729 +6
- Misses 11531 11533 +2
Partials 2486 2486
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Review SummaryInline comments posted
Prior review issues (not repeated)The cross-cutting concerns from my prior review still stand:
|
bb4d148 to
1a81a3a
Compare
|
Claude comments addressed |
| #[cfg(any(not(feature = "std"), fuzzing))] | ||
| let now = Duration::from_secs(self.highest_seen_timestamp.load(Ordering::Acquire) as u64); | ||
| #[cfg(feature = "std")] | ||
| #[cfg(all(feature = "std", not(fuzzing)))] |
There was a problem hiding this comment.
Shouldn't we just fuzz with no-std, then?
There was a problem hiding this comment.
We'd redefine no-std as being deterministic, seems like an undesirable shortcut.
There was a problem hiding this comment.
I meant just for the time logic - no-std is already defined as not supporting fetching the current time, seems reasonable to use that.
There was a problem hiding this comment.
It appears that deterministic hashes were already in the code for fuzzing, so the only change needed is then to switch to no std.
Claude review pointed out that no-std wasn't actually enabled. And enabling it runs into the issue that test_utils also needs to be made no-std compatible. Then it becomes a larger change.
There was a problem hiding this comment.
I am still in favor of the original change that adds the fuzzing flag to the time code.
1a81a3a to
9ffd8b3
Compare
fuzz/Cargo.toml
Outdated
|
|
||
| [dependencies] | ||
| lightning = { path = "../lightning", features = ["regex", "_test_utils"] } | ||
| lightning = { path = "../lightning", default-features = false, features = ["regex", "_test_utils"] } |
There was a problem hiding this comment.
Bug: This change is a no-op due to Cargo feature unification.
lightning-persister on line 25 is included without default-features = false:
lightning-persister = { path = "../lightning-persister", features = ["tokio"]}
And lightning-persister/Cargo.toml depends on lightning with default features:
lightning = { version = "0.3.0", path = "../lightning" }Lightning's defaults are ["std", "grind_signatures"]. Through Cargo feature unification, lightning-persister's dependency re-enables both std and grind_signatures on the unified lightning crate, making your default-features = false here have no effect.
Similarly, lightning-liquidity (line 23) uses default features which include std = ["lightning/std"], also re-enabling lightning/std.
To actually disable grind_signatures, you'd need to ensure no transitive dependency pulls in lightning with default features. At minimum, lightning-persister/Cargo.toml itself would need to be updated to use default-features = false for its lightning dependency (and explicitly enable only the features it needs, likely std).
9ffd8b3 to
1a81a3a
Compare
Gate all SystemTime::now() and Instant::now() calls in production code with #[cfg(all(feature = "std", not(fuzzing)))] so that fuzz targets produce consistent results regardless of wall-clock time. For each location, the existing no-std fallback (highest_seen_timestamp, None, or a constant) is reused under fuzzing. Also force deterministic hashing when the fuzzing cfg is active, rather than requiring the LDK_TEST_DETERMINISTIC_HASHES env var. AI tools were used in preparing this commit.
1a81a3a to
9f4e929
Compare
| /// This method is only available with the `std` feature. See | ||
| /// This method is only available with the `std` feature (and not during fuzzing). See | ||
| /// [`NetworkGraph::remove_stale_channels_and_tracking_with_time`] for non-`std` use. | ||
| #[cfg(not(fuzzing))] |
There was a problem hiding this comment.
The new #[cfg(not(fuzzing))] here is split from the pre-existing #[cfg(feature = "std")] on line 2306, 17 lines above (separated by the entire doc comment). In Rust, multiple #[cfg] attributes are ANDed, so the effective gate is all(feature = "std", not(fuzzing)) — which is correct. But every other location in this PR uses a single combined attribute like #[cfg(all(feature = "std", not(fuzzing)))].
Having the two conditions split across 17 lines makes it easy to overlook one when auditing. Consider merging them into one attribute and moving it closer to the function:
| #[cfg(not(fuzzing))] | |
| #[cfg(all(feature = "std", not(fuzzing)))] |
(and removing the #[cfg(feature = "std")] on line 2306 above the doc comment)
Use deterministic time and hash table ordering when building with cfg(fuzzing) to ensure fuzz test cases reproduce consistently.