Skip to content

Commit 69edc09

Browse files
committed
Make fuzz targets deterministic
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.
1 parent 450c03a commit 69edc09

8 files changed

Lines changed: 50 additions & 38 deletions

File tree

lightning/src/ln/channel.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16717,10 +16717,10 @@ impl<'a, 'b, 'c, ES: EntropySource, SP: SignerProvider>
1671716717
}
1671816718

1671916719
fn duration_since_epoch() -> Option<Duration> {
16720-
#[cfg(not(feature = "std"))]
16720+
#[cfg(any(not(feature = "std"), fuzzing))]
1672116721
let now = None;
1672216722

16723-
#[cfg(feature = "std")]
16723+
#[cfg(all(feature = "std", not(fuzzing)))]
1672416724
let now = Some(
1672516725
std::time::SystemTime::now()
1672616726
.duration_since(std::time::SystemTime::UNIX_EPOCH)

lightning/src/ln/channelmanager.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8936,11 +8936,11 @@ impl<
89368936
let _ = self.handle_error(err, counterparty_node_id);
89378937
}
89388938

8939-
#[cfg(feature = "std")]
8939+
#[cfg(all(feature = "std", not(fuzzing)))]
89408940
let duration_since_epoch = std::time::SystemTime::now()
89418941
.duration_since(std::time::SystemTime::UNIX_EPOCH)
89428942
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");
8943-
#[cfg(not(feature = "std"))]
8943+
#[cfg(any(not(feature = "std"), fuzzing))]
89448944
let duration_since_epoch = Duration::from_secs(
89458945
self.highest_seen_timestamp.load(Ordering::Acquire).saturating_sub(7200) as u64,
89468946
);
@@ -14129,7 +14129,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1412914129
let currency =
1413014130
Network::from_chain_hash(self.chain_hash).map(Into::into).unwrap_or(Currency::Bitcoin);
1413114131

14132-
#[cfg(feature = "std")]
14132+
#[cfg(all(feature = "std", not(fuzzing)))]
1413314133
let duration_since_epoch = {
1413414134
use std::time::SystemTime;
1413514135
SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)
@@ -14139,7 +14139,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1413914139
// This may be up to 2 hours in the future because of bitcoin's block time rule or about
1414014140
// 10-30 minutes in the past if a block hasn't been found recently. This should be fine as
1414114141
// the default invoice expiration is 2 hours, though shorter expirations may be problematic.
14142-
#[cfg(not(feature = "std"))]
14142+
#[cfg(any(not(feature = "std"), fuzzing))]
1414314143
let duration_since_epoch =
1414414144
Duration::from_secs(self.highest_seen_timestamp.load(Ordering::Acquire) as u64);
1414514145

@@ -14996,9 +14996,9 @@ impl<
1499614996
}
1499714997

1499814998
pub(super) fn duration_since_epoch(&self) -> Duration {
14999-
#[cfg(not(feature = "std"))]
14999+
#[cfg(any(not(feature = "std"), fuzzing))]
1500015000
let now = Duration::from_secs(self.highest_seen_timestamp.load(Ordering::Acquire) as u64);
15001-
#[cfg(feature = "std")]
15001+
#[cfg(all(feature = "std", not(fuzzing)))]
1500215002
let now = std::time::SystemTime::now()
1500315003
.duration_since(std::time::SystemTime::UNIX_EPOCH)
1500415004
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");

lightning/src/ln/outbound_payment.rs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -446,14 +446,16 @@ impl Retry {
446446
(Retry::Attempts(max_retry_count), PaymentAttempts { count, .. }) => {
447447
max_retry_count > count
448448
},
449-
#[cfg(feature = "std")]
449+
#[cfg(all(feature = "std", not(fuzzing)))]
450450
(Retry::Timeout(max_duration), PaymentAttempts { first_attempted_at, .. }) =>
451451
*max_duration >= Instant::now().duration_since(*first_attempted_at),
452+
#[cfg(all(feature = "std", fuzzing))]
453+
(Retry::Timeout(_), _) => true,
452454
}
453455
}
454456
}
455457

456-
#[cfg(feature = "std")]
458+
#[cfg(all(feature = "std", not(fuzzing)))]
457459
#[rustfmt::skip]
458460
pub(super) fn has_expired(route_params: &RouteParameters) -> bool {
459461
if let Some(expiry_time) = route_params.payment_params.expiry_time {
@@ -464,32 +466,38 @@ pub(super) fn has_expired(route_params: &RouteParameters) -> bool {
464466
false
465467
}
466468

469+
#[cfg(all(feature = "std", fuzzing))]
470+
#[rustfmt::skip]
471+
pub(super) fn has_expired(_route_params: &RouteParameters) -> bool {
472+
false
473+
}
474+
467475
/// Storing minimal payment attempts information required for determining if a outbound payment can
468476
/// be retried.
469477
pub(crate) struct PaymentAttempts {
470478
/// This count will be incremented only after the result of the attempt is known. When it's 0,
471479
/// it means the result of the first attempt is not known yet.
472480
pub(crate) count: u32,
473481
/// This field is only used when retry is `Retry::Timeout` which is only build with feature std
474-
#[cfg(feature = "std")]
482+
#[cfg(all(feature = "std", not(fuzzing)))]
475483
first_attempted_at: Instant,
476484
}
477485

478486
impl PaymentAttempts {
479487
pub(crate) fn new() -> Self {
480488
PaymentAttempts {
481489
count: 0,
482-
#[cfg(feature = "std")]
490+
#[cfg(all(feature = "std", not(fuzzing)))]
483491
first_attempted_at: Instant::now(),
484492
}
485493
}
486494
}
487495

488496
impl Display for PaymentAttempts {
489497
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
490-
#[cfg(not(feature = "std"))]
498+
#[cfg(any(not(feature = "std"), fuzzing))]
491499
return write!(f, "attempts: {}", self.count);
492-
#[cfg(feature = "std")]
500+
#[cfg(all(feature = "std", not(fuzzing)))]
493501
return write!(
494502
f,
495503
"attempts: {}, duration: {}s",

lightning/src/ln/peer_handler.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2327,7 +2327,7 @@ impl<
23272327

23282328
#[allow(unused_mut)]
23292329
let mut should_do_full_sync = true;
2330-
#[cfg(feature = "std")]
2330+
#[cfg(all(feature = "std", not(fuzzing)))]
23312331
{
23322332
// Forward ad-hoc gossip if the timestamp range is less than six hours ago.
23332333
// Otherwise, do a full sync.

lightning/src/offers/flow.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -183,9 +183,9 @@ impl<MR: MessageRouter, L: Logger> OffersMessageFlow<MR, L> {
183183
}
184184

185185
fn duration_since_epoch(&self) -> Duration {
186-
#[cfg(not(feature = "std"))]
186+
#[cfg(any(not(feature = "std"), fuzzing))]
187187
let now = Duration::from_secs(self.highest_seen_timestamp.load(Ordering::Acquire) as u64);
188-
#[cfg(feature = "std")]
188+
#[cfg(all(feature = "std", not(fuzzing)))]
189189
let now = std::time::SystemTime::now()
190190
.duration_since(std::time::SystemTime::UNIX_EPOCH)
191191
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");
@@ -942,17 +942,17 @@ impl<MR: MessageRouter, L: Logger> OffersMessageFlow<MR, L> {
942942
)
943943
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
944944

945-
#[cfg(feature = "std")]
945+
#[cfg(all(feature = "std", not(fuzzing)))]
946946
let builder = refund.respond_using_derived_keys(
947947
payment_paths,
948948
payment_hash,
949949
expanded_key,
950950
entropy,
951951
)?;
952952

953-
#[cfg(not(feature = "std"))]
953+
#[cfg(any(not(feature = "std"), fuzzing))]
954954
let created_at = Duration::from_secs(self.highest_seen_timestamp.load(Ordering::Acquire) as u64);
955-
#[cfg(not(feature = "std"))]
955+
#[cfg(any(not(feature = "std"), fuzzing))]
956956
let builder = refund.respond_using_derived_keys_no_std(
957957
payment_paths,
958958
payment_hash,
@@ -1008,9 +1008,9 @@ impl<MR: MessageRouter, L: Logger> OffersMessageFlow<MR, L> {
10081008
)
10091009
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
10101010

1011-
#[cfg(feature = "std")]
1011+
#[cfg(all(feature = "std", not(fuzzing)))]
10121012
let builder = invoice_request.respond_using_derived_keys(payment_paths, payment_hash);
1013-
#[cfg(not(feature = "std"))]
1013+
#[cfg(any(not(feature = "std"), fuzzing))]
10141014
let builder = invoice_request.respond_using_derived_keys_no_std(
10151015
payment_paths,
10161016
payment_hash,
@@ -1067,9 +1067,9 @@ impl<MR: MessageRouter, L: Logger> OffersMessageFlow<MR, L> {
10671067
)
10681068
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
10691069

1070-
#[cfg(feature = "std")]
1070+
#[cfg(all(feature = "std", not(fuzzing)))]
10711071
let builder = invoice_request.respond_with(payment_paths, payment_hash);
1072-
#[cfg(not(feature = "std"))]
1072+
#[cfg(any(not(feature = "std"), fuzzing))]
10731073
let builder = invoice_request.respond_with_no_std(
10741074
payment_paths,
10751075
payment_hash,

lightning/src/onion_message/dns_resolution.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -501,7 +501,7 @@ impl OMNameResolver {
501501
if let Ok(validated_rrs) = validated_rrs {
502502
#[allow(unused_assignments, unused_mut)]
503503
let mut time = self.latest_block_time.load(Ordering::Acquire) as u64;
504-
#[cfg(feature = "std")]
504+
#[cfg(all(feature = "std", not(fuzzing)))]
505505
{
506506
use std::time::{SystemTime, UNIX_EPOCH};
507507
let now = SystemTime::now().duration_since(UNIX_EPOCH);
@@ -512,7 +512,8 @@ impl OMNameResolver {
512512
// (we assume no more than two hours, though the actual limits are rather
513513
// complicated).
514514
// Thus, we have to let the proof times be rather fuzzy.
515-
let max_time_offset = if cfg!(feature = "std") { 0 } else { 60 * 2 };
515+
let max_time_offset =
516+
if cfg!(all(feature = "std", not(fuzzing))) { 0 } else { 60 * 2 };
516517
if validated_rrs.valid_from > time + max_time_offset {
517518
return None;
518519
}

lightning/src/routing/gossip.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -843,7 +843,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, U: UtxoLookup, L: Logger> BaseMessageHa
843843
let mut gossip_start_time = 0;
844844
#[allow(unused)]
845845
let should_sync = self.should_request_full_sync();
846-
#[cfg(feature = "std")]
846+
#[cfg(all(feature = "std", not(fuzzing)))]
847847
{
848848
gossip_start_time = SystemTime::now()
849849
.duration_since(UNIX_EPOCH)
@@ -2195,7 +2195,7 @@ impl<L: Logger> NetworkGraph<L> {
21952195

21962196
#[allow(unused_mut, unused_assignments)]
21972197
let mut announcement_received_time = 0;
2198-
#[cfg(feature = "std")]
2198+
#[cfg(all(feature = "std", not(fuzzing)))]
21992199
{
22002200
announcement_received_time = SystemTime::now()
22012201
.duration_since(UNIX_EPOCH)
@@ -2235,11 +2235,11 @@ impl<L: Logger> NetworkGraph<L> {
22352235
///
22362236
/// The channel and any node for which this was their last channel are removed from the graph.
22372237
pub fn channel_failed_permanent(&self, short_channel_id: u64) {
2238-
#[cfg(feature = "std")]
2238+
#[cfg(all(feature = "std", not(fuzzing)))]
22392239
let current_time_unix = Some(
22402240
SystemTime::now().duration_since(UNIX_EPOCH).expect("Time must be > 1970").as_secs(),
22412241
);
2242-
#[cfg(not(feature = "std"))]
2242+
#[cfg(any(not(feature = "std"), fuzzing))]
22432243
let current_time_unix = None;
22442244

22452245
self.channel_failed_permanent_with_time(short_channel_id, current_time_unix)
@@ -2262,11 +2262,11 @@ impl<L: Logger> NetworkGraph<L> {
22622262
/// Marks a node in the graph as permanently failed, effectively removing it and its channels
22632263
/// from local storage.
22642264
pub fn node_failed_permanent(&self, node_id: &PublicKey) {
2265-
#[cfg(feature = "std")]
2265+
#[cfg(all(feature = "std", not(fuzzing)))]
22662266
let current_time_unix = Some(
22672267
SystemTime::now().duration_since(UNIX_EPOCH).expect("Time must be > 1970").as_secs(),
22682268
);
2269-
#[cfg(not(feature = "std"))]
2269+
#[cfg(any(not(feature = "std"), fuzzing))]
22702270
let current_time_unix = None;
22712271

22722272
let node_id = NodeId::from_pubkey(node_id);
@@ -2303,7 +2303,6 @@ impl<L: Logger> NetworkGraph<L> {
23032303
}
23042304
}
23052305

2306-
#[cfg(feature = "std")]
23072306
/// Removes information about channels that we haven't heard any updates about in some time.
23082307
/// This can be used regularly to prune the network graph of channels that likely no longer
23092308
/// exist.
@@ -2318,8 +2317,9 @@ impl<L: Logger> NetworkGraph<L> {
23182317
/// This method will also cause us to stop tracking removed nodes and channels if they have been
23192318
/// in the map for a while so that these can be resynced from gossip in the future.
23202319
///
2321-
/// This method is only available with the `std` feature. See
2320+
/// This method is only available with the `std` feature (and not during fuzzing). See
23222321
/// [`NetworkGraph::remove_stale_channels_and_tracking_with_time`] for non-`std` use.
2322+
#[cfg(all(feature = "std", not(fuzzing)))]
23232323
pub fn remove_stale_channels_and_tracking(&self) {
23242324
let time =
23252325
SystemTime::now().duration_since(UNIX_EPOCH).expect("Time must be > 1970").as_secs();
@@ -2476,7 +2476,7 @@ impl<L: Logger> NetworkGraph<L> {
24762476
});
24772477
}
24782478

2479-
#[cfg(all(feature = "std", not(test), not(feature = "_test_utils")))]
2479+
#[cfg(all(feature = "std", not(test), not(feature = "_test_utils"), not(fuzzing)))]
24802480
{
24812481
// Note that many tests rely on being able to set arbitrarily old timestamps, thus we
24822482
// disable this check during tests!

lightning/src/util/hash_tables.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
pub use hashbrown::hash_map;
77

88
mod hashbrown_tables {
9-
#[cfg(all(feature = "std", not(test)))]
9+
#[cfg(all(feature = "std", not(test), not(fuzzing)))]
1010
mod hasher {
1111
pub use std::collections::hash_map::RandomState;
1212
}
13-
#[cfg(all(feature = "std", test))]
13+
#[cfg(all(feature = "std", any(test, fuzzing)))]
1414
mod hasher {
1515
#![allow(deprecated)] // hash::SipHasher was deprecated in favor of something only in std.
1616
use core::hash::{BuildHasher, Hasher};
@@ -27,7 +27,10 @@ mod hashbrown_tables {
2727

2828
impl RandomState {
2929
pub fn new() -> RandomState {
30-
if std::env::var("LDK_TEST_DETERMINISTIC_HASHES").map(|v| v == "1").unwrap_or(false)
30+
if cfg!(fuzzing)
31+
|| std::env::var("LDK_TEST_DETERMINISTIC_HASHES")
32+
.map(|v| v == "1")
33+
.unwrap_or(false)
3134
{
3235
RandomState::Deterministic
3336
} else {

0 commit comments

Comments
 (0)