Add trait for saturating arithmetic (#15812)

* Add SaturatingArithmetic trait

* Use Duration saturating arithmetic

* Use new macro to fix poh_config
This commit is contained in:
Tyera Eulberg 2021-03-11 23:22:40 -07:00 committed by GitHub
parent cc38ae72e7
commit e5b644e830
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 61 additions and 11 deletions

45
sdk/src/arithmetic.rs Normal file
View File

@ -0,0 +1,45 @@
use std::time::Duration;
/// A helper trait for primitive types that do not yet implement saturating arithmetic methods
pub trait SaturatingArithmetic<T> {
fn sol_saturating_add(&self, rhs: Self) -> Self;
fn sol_saturating_sub(&self, rhs: Self) -> Self;
fn sol_saturating_mul(&self, rhs: T) -> Self;
}
/// Saturating arithmetic for Duration, until Rust support moves from nightly to stable
/// Duration::MAX is constructed manually, as Duration consts are not yet stable either.
impl SaturatingArithmetic<u32> for Duration {
fn sol_saturating_add(&self, rhs: Self) -> Self {
self.checked_add(rhs)
.unwrap_or_else(|| Self::new(u64::MAX, 1_000_000_000u32.saturating_sub(1)))
}
fn sol_saturating_sub(&self, rhs: Self) -> Self {
self.checked_sub(rhs).unwrap_or_else(|| Self::new(0, 0))
}
fn sol_saturating_mul(&self, rhs: u32) -> Self {
self.checked_mul(rhs)
.unwrap_or_else(|| Self::new(u64::MAX, 1_000_000_000u32.saturating_sub(1)))
}
}
#[cfg(test)]
pub mod tests {
use super::*;
#[test]
fn test_duration() {
let empty_duration = Duration::new(0, 0);
let max_duration = Duration::new(u64::MAX, 1_000_000_000 - 1);
let duration = Duration::new(u64::MAX, 0);
let add = duration.sol_saturating_add(duration);
assert_eq!(add, max_duration);
let sub = duration.sol_saturating_sub(max_duration);
assert_eq!(sub, empty_duration);
let mult = duration.sol_saturating_mul(u32::MAX);
assert_eq!(mult, max_duration);
}
}

View File

@ -8,6 +8,7 @@ pub use solana_program::*;
pub mod account; pub mod account;
pub mod account_utils; pub mod account_utils;
pub mod arithmetic;
pub mod builtins; pub mod builtins;
pub mod client; pub mod client;
pub mod commitment_config; pub mod commitment_config;

View File

@ -1,5 +1,4 @@
#![allow(clippy::integer_arithmetic)] use crate::{clock::DEFAULT_TICKS_PER_SECOND, unchecked_div_by_const};
use crate::clock::DEFAULT_TICKS_PER_SECOND;
use std::time::Duration; use std::time::Duration;
#[derive(Serialize, Deserialize, Clone, Debug, AbiExample)] #[derive(Serialize, Deserialize, Clone, Debug, AbiExample)]
@ -29,8 +28,9 @@ impl PohConfig {
impl Default for PohConfig { impl Default for PohConfig {
fn default() -> Self { fn default() -> Self {
Self::new_sleep(Duration::from_micros( Self::new_sleep(Duration::from_micros(unchecked_div_by_const!(
1000 * 1000 / DEFAULT_TICKS_PER_SECOND, 1000 * 1000,
)) DEFAULT_TICKS_PER_SECOND
)))
} }
} }

View File

@ -1,6 +1,7 @@
/// A helper for calculating a stake-weighted timestamp estimate from a set of timestamps and epoch /// A helper for calculating a stake-weighted timestamp estimate from a set of timestamps and epoch
/// stake. /// stake.
use solana_sdk::{ use solana_sdk::{
arithmetic::SaturatingArithmetic,
clock::{Slot, UnixTimestamp}, clock::{Slot, UnixTimestamp},
pubkey::Pubkey, pubkey::Pubkey,
}; };
@ -43,7 +44,7 @@ where
let mut total_stake: u128 = 0; let mut total_stake: u128 = 0;
for (vote_pubkey, slot_timestamp) in unique_timestamps { for (vote_pubkey, slot_timestamp) in unique_timestamps {
let (timestamp_slot, timestamp) = slot_timestamp.borrow(); let (timestamp_slot, timestamp) = slot_timestamp.borrow();
let offset = slot.saturating_sub(*timestamp_slot) as u32 * slot_duration; let offset = slot_duration.sol_saturating_mul(slot.saturating_sub(*timestamp_slot) as u32);
let estimate = timestamp.saturating_add(offset.as_secs() as i64); let estimate = timestamp.saturating_add(offset.as_secs() as i64);
let stake = stakes let stake = stakes
.get(vote_pubkey.borrow()) .get(vote_pubkey.borrow())
@ -70,16 +71,19 @@ where
} }
// Bound estimate by `max_allowable_drift` since the start of the epoch // Bound estimate by `max_allowable_drift` since the start of the epoch
if let Some((epoch_start_slot, epoch_start_timestamp)) = epoch_start_timestamp { if let Some((epoch_start_slot, epoch_start_timestamp)) = epoch_start_timestamp {
let poh_estimate_offset = slot.saturating_sub(epoch_start_slot) as u32 * slot_duration; let poh_estimate_offset =
slot_duration.sol_saturating_mul(slot.saturating_sub(epoch_start_slot) as u32);
let estimate_offset = Duration::from_secs(if fix_estimate_into_u64 { let estimate_offset = Duration::from_secs(if fix_estimate_into_u64 {
(estimate as u64).saturating_sub(epoch_start_timestamp as u64) (estimate as u64).saturating_sub(epoch_start_timestamp as u64)
} else { } else {
estimate.saturating_sub(epoch_start_timestamp) as u64 estimate.saturating_sub(epoch_start_timestamp) as u64
}); });
let max_allowable_drift_fast = poh_estimate_offset * max_allowable_drift.fast / 100; let max_allowable_drift_fast =
let max_allowable_drift_slow = poh_estimate_offset * max_allowable_drift.slow / 100; poh_estimate_offset.sol_saturating_mul(max_allowable_drift.fast) / 100;
let max_allowable_drift_slow =
poh_estimate_offset.sol_saturating_mul(max_allowable_drift.slow) / 100;
if estimate_offset > poh_estimate_offset if estimate_offset > poh_estimate_offset
&& estimate_offset - poh_estimate_offset > max_allowable_drift_slow && estimate_offset.sol_saturating_sub(poh_estimate_offset) > max_allowable_drift_slow
{ {
// estimate offset since the start of the epoch is higher than // estimate offset since the start of the epoch is higher than
// `MAX_ALLOWABLE_DRIFT_PERCENTAGE_SLOW` // `MAX_ALLOWABLE_DRIFT_PERCENTAGE_SLOW`
@ -87,7 +91,7 @@ where
.saturating_add(poh_estimate_offset.as_secs() as i64) .saturating_add(poh_estimate_offset.as_secs() as i64)
.saturating_add(max_allowable_drift_slow.as_secs() as i64); .saturating_add(max_allowable_drift_slow.as_secs() as i64);
} else if estimate_offset < poh_estimate_offset } else if estimate_offset < poh_estimate_offset
&& poh_estimate_offset - estimate_offset > max_allowable_drift_fast && poh_estimate_offset.sol_saturating_sub(estimate_offset) > max_allowable_drift_fast
{ {
// estimate offset since the start of the epoch is lower than // estimate offset since the start of the epoch is lower than
// `MAX_ALLOWABLE_DRIFT_PERCENTAGE_FAST` // `MAX_ALLOWABLE_DRIFT_PERCENTAGE_FAST`