Skip to content

Commit 16d014f

Browse files
committed
Simplify and fix AtomicCounter
`AtomicCounter` was slightly race-y on 32-bit platforms because it increments the high `AtomicUsize` independently from the low `AtomicUsize`, leading to a potential race where another thread could observe the low increment but not the high increment and see a value of 0 twice. This isn't a big deal because (a) most platforms are 64-bit these days, (b) 32-bit platforms aren't super likely to have their counter overflow 32 bits anyway, and (c) the two writes are back-to-back so having another thread read during that window is very unlikely. However, we can also optimize the counter somewhat by using the `target_has_atomic = "64"` cfg flag, which we do here, allowing us to use `AtomicU64` even on 32-bit platforms where 64-bit atomics are available. This changes some test behavior slightly, which requires adaptation. Fixes #3000
1 parent 7662624 commit 16d014f

File tree

4 files changed

+50
-20
lines changed

4 files changed

+50
-20
lines changed

lightning/src/ln/functional_tests.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -7668,8 +7668,8 @@ fn test_bump_penalty_txn_on_revoked_htlcs() {
76687668
assert_ne!(node_txn[0].input[0].previous_output, node_txn[2].input[0].previous_output);
76697669
assert_ne!(node_txn[1].input[0].previous_output, node_txn[2].input[0].previous_output);
76707670

7671-
assert_eq!(node_txn[0].input[0].previous_output, revoked_htlc_txn[1].input[0].previous_output);
7672-
assert_eq!(node_txn[1].input[0].previous_output, revoked_htlc_txn[0].input[0].previous_output);
7671+
assert_eq!(node_txn[1].input[0].previous_output, revoked_htlc_txn[1].input[0].previous_output);
7672+
assert_eq!(node_txn[0].input[0].previous_output, revoked_htlc_txn[0].input[0].previous_output);
76737673

76747674
// node_txn[3] spends the revoked outputs from the revoked_htlc_txn (which only have one
76757675
// output, checked above).

lightning/src/ln/monitor_tests.rs

+4
Original file line numberDiff line numberDiff line change
@@ -2251,6 +2251,10 @@ fn do_test_restored_packages_retry(check_old_monitor_retries_after_upgrade: bool
22512251

22522252
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
22532253

2254+
// Reset our RNG counters to mirror the RNG output from when this test was written.
2255+
nodes[0].keys_manager.backing.inner.entropy_source.set_counter(0x1_0000_0004);
2256+
nodes[1].keys_manager.backing.inner.entropy_source.set_counter(0x1_0000_0004);
2257+
22542258
// Open a channel, lock in an HTLC, and immediately broadcast the commitment transaction. This
22552259
// ensures that the HTLC timeout package is held until we reach its expiration height.
22562260
let (_, _, chan_id, funding_tx) = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100_000, 50_000_000);

lightning/src/sign/mod.rs

+13
Original file line numberDiff line numberDiff line change
@@ -1854,6 +1854,9 @@ pub struct KeysManager {
18541854
channel_master_key: Xpriv,
18551855
channel_child_index: AtomicUsize,
18561856

1857+
#[cfg(test)]
1858+
pub(crate) entropy_source: RandomBytes,
1859+
#[cfg(not(test))]
18571860
entropy_source: RandomBytes,
18581861

18591862
seed: [u8; 32],
@@ -2310,6 +2313,9 @@ impl SignerProvider for KeysManager {
23102313
/// Switching between this struct and [`KeysManager`] will invalidate any previously issued
23112314
/// invoices and attempts to pay previous invoices will fail.
23122315
pub struct PhantomKeysManager {
2316+
#[cfg(test)]
2317+
pub(crate) inner: KeysManager,
2318+
#[cfg(not(test))]
23132319
inner: KeysManager,
23142320
inbound_payment_key: KeyMaterial,
23152321
phantom_secret: SecretKey,
@@ -2489,6 +2495,13 @@ impl RandomBytes {
24892495
pub fn new(seed: [u8; 32]) -> Self {
24902496
Self { seed, index: AtomicCounter::new() }
24912497
}
2498+
2499+
#[cfg(test)]
2500+
/// Force the counter to a value to produce the same output again. Mostly useful in tests where
2501+
/// we need to maintain behavior with a previous version which didn't use as much RNG output.
2502+
pub(crate) fn set_counter(&self, count: u64) {
2503+
self.index.set_counter(count);
2504+
}
24922505
}
24932506

24942507
impl EntropySource for RandomBytes {

lightning/src/util/atomic_counter.rs

+31-18
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,45 @@
1-
//! A simple atomic counter that uses AtomicUsize to give a u64 counter.
1+
//! A simple atomic counter that uses mutexes if the platform doesn't support atomic u64s.
22
3-
#[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))]
4-
compile_error!("We need at least 32-bit pointers for atomic counter (and to have enough memory to run LDK)");
5-
6-
use core::sync::atomic::{AtomicUsize, Ordering};
3+
#[cfg(target_has_atomic = "64")]
4+
use core::sync::atomic::{AtomicU64, Ordering};
5+
#[cfg(not(target_has_atomic = "64"))]
6+
use crate::sync::Mutex;
77

88
#[derive(Debug)]
99
pub(crate) struct AtomicCounter {
10-
// Usize needs to be at least 32 bits to avoid overflowing both low and high. If usize is 64
11-
// bits we will never realistically count into high:
12-
counter_low: AtomicUsize,
13-
counter_high: AtomicUsize,
10+
#[cfg(target_has_atomic = "64")]
11+
counter: AtomicU64,
12+
#[cfg(not(target_has_atomic = "64"))]
13+
counter: Mutex<u64>,
1414
}
1515

1616
impl AtomicCounter {
1717
pub(crate) fn new() -> Self {
1818
Self {
19-
counter_low: AtomicUsize::new(0),
20-
counter_high: AtomicUsize::new(0),
19+
#[cfg(target_has_atomic = "64")]
20+
counter: AtomicU64::new(0),
21+
#[cfg(not(target_has_atomic = "64"))]
22+
counter: Mutex::new(0),
2123
}
2224
}
2325
pub(crate) fn get_increment(&self) -> u64 {
24-
let low = self.counter_low.fetch_add(1, Ordering::AcqRel) as u64;
25-
let high = if low == 0 {
26-
self.counter_high.fetch_add(1, Ordering::AcqRel) as u64
27-
} else {
28-
self.counter_high.load(Ordering::Acquire) as u64
29-
};
30-
(high << 32) | low
26+
#[cfg(target_has_atomic = "64")] {
27+
self.counter.fetch_add(1, Ordering::AcqRel)
28+
}
29+
#[cfg(not(target_has_atomic = "64"))] {
30+
let mtx = self.counter.lock().unwrap();
31+
*mtx += 1;
32+
*mtx - 1
33+
}
34+
}
35+
#[cfg(test)]
36+
pub(crate) fn set_counter(&self, count: u64) {
37+
#[cfg(target_has_atomic = "64")] {
38+
self.counter.store(count, Ordering::Release);
39+
}
40+
#[cfg(not(target_has_atomic = "64"))] {
41+
let mtx = self.counter.lock().unwrap();
42+
*mtx = count;
43+
}
3144
}
3245
}

0 commit comments

Comments
 (0)