Skip to content

Commit 59aa800

Browse files
authored
fix: safe arithmetic in timeout calculation (#609)
1 parent 36e951a commit 59aa800

File tree

3 files changed

+31
-6
lines changed

3 files changed

+31
-6
lines changed

anchor/common/qbft/src/lib.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,16 @@ where
220220
self.current_round
221221
}
222222

223+
/// Get the current instance identifier
224+
pub fn get_identifier(&self) -> &MessageId {
225+
&self.identifier
226+
}
227+
228+
/// Get the current instance height
229+
pub fn get_instance_height(&self) -> InstanceHeight {
230+
self.instance_height
231+
}
232+
223233
// Shifts this instance into a new round>
224234
fn set_round(&mut self, new_round: Round) {
225235
self.current_round.set(new_round);

anchor/qbft_manager/src/instance.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,19 @@ impl<D: QbftData<Hash = Hash256>> Initialized<D> {
175175
// We calculate the sleep dynamically, as both messages and the local timer might cause the
176176
// round to advance
177177
let round_end = calculate_round_timeout(self.qbft.get_round().into(), &self.start_time);
178-
let round_timeout_sleep = tokio::time::sleep_until(round_end);
178+
179+
let Some(timeout_instant) = round_end else {
180+
error!(
181+
"Round timeout calculation overflowed for round {}, stopping the instance. \
182+
QBFT identifier: {:?}, instance height: {:?}",
183+
self.qbft.get_round(),
184+
self.qbft.get_identifier(),
185+
self.qbft.get_instance_height()
186+
);
187+
return RecvResult::Closed;
188+
};
189+
190+
let round_timeout_sleep = tokio::time::sleep_until(timeout_instant);
179191
tokio::pin!(round_timeout_sleep);
180192

181193
select! {

anchor/qbft_manager/src/timeout.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,24 @@ const QUICK_TIMEOUT_THRESHOLD: u64 = 8; // Round 8
66
const QUICK_TIMEOUT: u64 = 2; // 2 Seconds
77
const SLOW_TIMEOUT: u64 = 120; // 2 Minutes
88

9-
pub fn calculate_round_timeout(round: u64, start_time: &Instant) -> Instant {
9+
pub fn calculate_round_timeout(round: u64, start_time: &Instant) -> Option<Instant> {
1010
let additional_timeout = if round <= QUICK_TIMEOUT_THRESHOLD {
1111
// If we are below the quick timeout threshold the additonal timeout is round *
1212
// QUICK_TIMEOUT
13-
Duration::from_secs(round * QUICK_TIMEOUT)
13+
Duration::from_secs(round.checked_mul(QUICK_TIMEOUT)?)
1414
} else {
1515
// For higher rounds, use a combination of quick and slow timeouts
1616

1717
// The quick portion is the timeout threshold * QUICK_TIMEOUT
1818
let quick_portion = Duration::from_secs(QUICK_TIMEOUT_THRESHOLD * QUICK_TIMEOUT);
1919

2020
// The slow poritin is (round - threshold) * SLOW_TIMEOUT
21-
let slow_portion = Duration::from_secs((round - QUICK_TIMEOUT_THRESHOLD) * SLOW_TIMEOUT);
22-
quick_portion + slow_portion
21+
let slow_portion = Duration::from_secs(
22+
(round.checked_sub(QUICK_TIMEOUT_THRESHOLD))?.checked_mul(SLOW_TIMEOUT)?,
23+
);
24+
25+
quick_portion.checked_add(slow_portion)?
2326
};
2427

25-
*start_time + additional_timeout
28+
start_time.checked_add(additional_timeout)
2629
}

0 commit comments

Comments
 (0)