use crate::client::Web3;
use clarity::Uint256;
use std::cmp::Ordering;
use std::collections::VecDeque;
use std::time::Instant;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GasPriceEntry {
pub sample_time: Instant,
pub sample: Uint256,
}
impl GasPriceEntry {
pub fn new(sample: Uint256) -> Self {
GasPriceEntry {
sample_time: Instant::now(),
sample,
}
}
}
impl Ord for GasPriceEntry {
fn cmp(&self, other: &Self) -> Ordering {
let size1 = &self.sample;
let size2 = &other.sample;
if size1 < size2 {
return Ordering::Less;
}
if size1 > size2 {
return Ordering::Greater;
}
Ordering::Equal
}
}
impl PartialOrd for GasPriceEntry {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct GasTracker {
history: VecDeque<GasPriceEntry>,
size: usize,
}
impl GasTracker {
pub fn new(size: usize) -> Self {
GasTracker {
history: VecDeque::new(),
size,
}
}
pub fn get_current_size(&self) -> usize {
self.history.len()
}
pub fn get_history(&self) -> VecDeque<GasPriceEntry> {
self.history.clone()
}
pub fn expand_history_size(&mut self, size: usize) {
if self.history.len() > size {
return;
}
self.size = size;
}
pub fn latest_gas_price(&self) -> Option<Uint256> {
self.history.front().map(|price| price.sample.clone())
}
pub async fn sample(web30: &Web3) -> Option<GasPriceEntry> {
match web30.eth_gas_price().await {
Ok(price) => Some(GasPriceEntry::new(price)),
Err(e) => {
warn!("Unable to sample gas prices with: {:?}", e);
None
}
}
}
pub fn update(&mut self, sample: GasPriceEntry) {
match self.history.len().cmp(&self.size) {
Ordering::Less => {
self.history.push_front(sample);
}
Ordering::Equal => {
self.history.pop_back();
self.history.push_front(sample);
}
Ordering::Greater => {
panic!("Vec size greater than max size, error in GasTracker vecDeque logic")
}
}
}
pub async fn sample_and_update(&mut self, web30: &Web3) -> Option<Uint256> {
let sample = GasTracker::sample(web30).await;
match sample {
Some(entry) => {
self.update(entry.clone());
Some(entry.sample)
}
None => {
warn!("Failed to update gas price sample");
None
}
}
}
pub fn get_acceptable_gas_price(&self, percentage: f32) -> Option<Uint256> {
if self.history.is_empty() {
return None;
}
let mut vector: Vec<&GasPriceEntry> = Vec::from_iter(self.history.iter());
vector.sort();
let lowest: usize = (percentage * vector.len() as f32).floor() as usize;
Some(vector[lowest].sample.clone())
}
}
#[test]
fn test_gas_storage() {
use actix::System;
use futures::future::join;
use std::time::Duration;
let runner = System::new();
let web3 = Web3::new("https://eth.althea.net", Duration::from_secs(5));
runner.block_on(async move {
let mut tracker = GasTracker::new(10);
let gas_fut = web3.eth_gas_price();
let track_fut = tracker.sample_and_update(&web3);
let (gas, track) = join(gas_fut, track_fut).await;
let gas = gas.expect("Actix failure");
assert!(
track.is_some() && gas == track.clone().unwrap(),
"bad gas price stored - actual {} != stored {:?}",
gas,
track
);
});
}
#[test]
fn test_acceptable_gas_price() {
use std::time::Instant;
let history_values: Vec<u8> = vec![
33, 67, 22, 57, 78, 1, 56, 49, 81, 18, 17, 7, 50, 99, 84, 89, 13, 59, 14, 27, 75, 24, 82,
63, 31, 2, 4, 41, 79, 92, 45, 20, 30, 34, 25, 64, 21, 0, 86, 46, 32, 19, 11, 51, 71, 70,
62, 29, 35, 88, 94, 77, 43, 9, 65, 44, 69, 8, 90, 16, 58, 97, 87, 83, 15, 12, 61, 60, 48,
37, 73, 53, 74, 95, 98, 96, 23, 93, 91, 10, 40, 66, 42, 5, 36, 55, 54, 72, 47, 39, 28, 85,
6, 3, 76, 38, 80, 68, 52, 26,
];
let history = history_values.iter().map(|v| GasPriceEntry {
sample: (*v).into(),
sample_time: Instant::now(),
});
let tracker = GasTracker {
history: VecDeque::from_iter(history),
size: 100,
};
for i in history_values {
if i == 0 {
continue;
}
let expect = f32::from(i).floor();
let percent = expect / 100.0;
let expected_high = Uint256::from((expect as u32) + 1u32);
let expected_low = Uint256::from((expect as u32) - 1u32);
let acceptable = tracker.get_acceptable_gas_price(percent);
assert!(
acceptable.is_some(),
"got None from get_acceptable_gas_price with nonempty history"
);
let acceptable = acceptable.unwrap();
assert!(
acceptable <= expected_high && acceptable >= expected_low,
"percentage {:.8} expected range [{:?} <= {:?} <= {:?}]",
percent,
expected_low,
acceptable,
expected_high,
)
}
}