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.09% -0.01%
==========================================
Files 163 163
Lines 108740 108774 +34
Branches 108740 108774 +34
==========================================
+ Hits 94723 94742 +19
- Misses 11531 11544 +13
- Partials 2486 2488 +2
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:
|
|
The serialization for I've completed my thorough review. My prior review comments cover all the significant issues. I don't see any new bugs or issues beyond what was already flagged. Review SummaryAll issues from the prior review pass remain applicable. No new issues found beyond what was already covered. Prior inline comments (still applicable, not re-posted)
Cross-cutting concerns (unchanged from prior review)
Verification of this review passAll files and hunks in the diff were examined. The cfg gating pattern ( |
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).
1a81a3a to
9f4e929
Compare
9f4e929 to
35fb66b
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.
35fb66b to
69edc09
Compare
| #[cfg(all(feature = "std", fuzzing))] | ||
| (Retry::Timeout(_), _) => true, |
There was a problem hiding this comment.
Bug (latent): Under std + fuzzing, Retry::Timeout is always retryable. But OptionalBolt11PaymentParams::default() and OptionalOfferPaymentParams::default() (channelmanager.rs lines ~709 and ~747) still use Retry::Timeout(2s) when std is enabled — they aren't gated on not(fuzzing).
This means any code path that uses the default retry strategy during fuzzing will retry payments indefinitely (never hitting RetriesExhausted). Current fuzz targets explicitly use Retry::Attempts(2) so they're unaffected, but this is a footgun for future fuzz targets.
Consider updating those defaults to also use Retry::Attempts(3) under fuzzing:
#[cfg(all(feature = "std", not(fuzzing)))]
retry_strategy: Retry::Timeout(core::time::Duration::from_secs(2)),
#[cfg(any(not(feature = "std"), fuzzing))]
retry_strategy: Retry::Attempts(3),
Use deterministic time and hash table ordering when building with cfg(fuzzing) to ensure fuzz test cases reproduce consistently.