feat(gas_oracle): Add Lru Cache for block values of recent blocks (#5062)

Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
PatStiles
2023-10-18 06:04:41 -05:00
committed by GitHub
parent a25161fb9f
commit 9d9de58057
3 changed files with 59 additions and 13 deletions

1
Cargo.lock generated
View File

@ -6184,6 +6184,7 @@ dependencies = [
"assert_matches",
"async-trait",
"bytes",
"derive_more",
"futures",
"http",
"http-body",

View File

@ -68,6 +68,7 @@ tracing.workspace = true
tracing-futures = "0.2"
schnellru = "0.2"
futures.workspace = true
derive_more = "0.99"
[dev-dependencies]
jsonrpsee = { workspace = true, features = ["client"] }

View File

@ -4,9 +4,12 @@ use crate::eth::{
cache::EthStateCache,
error::{EthApiError, EthResult, RpcInvalidTransactionError},
};
use derive_more::{Deref, DerefMut};
use reth_primitives::{constants::GWEI_TO_WEI, BlockNumberOrTag, B256, U256};
use reth_provider::BlockReaderIdExt;
use schnellru::{ByLength, LruMap};
use serde::{Deserialize, Serialize};
use std::fmt::{self, Debug, Formatter};
use tokio::sync::Mutex;
use tracing::warn;
@ -90,8 +93,9 @@ pub struct GasPriceOracle<Provider> {
oracle_config: GasPriceOracleConfig,
/// The price under which the sample will be ignored.
ignore_price: Option<u128>,
/// The latest calculated price and its block hash
last_price: Mutex<GasPriceOracleResult>,
/// Stores the latest calculated price and its block hash and Cache stores the lowest effective
/// tip values of recent blocks
inner: Mutex<GasPriceOracleInner>,
}
impl<Provider> GasPriceOracle<Provider>
@ -111,7 +115,16 @@ where
}
let ignore_price = oracle_config.ignore_price.map(|price| price.saturating_to());
Self { provider, oracle_config, last_price: Default::default(), cache, ignore_price }
// this is the number of blocks that we will cache the values for
let cached_values = (oracle_config.blocks * 5).max(oracle_config.max_block_history as u32);
let inner = Mutex::new(GasPriceOracleInner {
last_price: Default::default(),
lowest_effective_tip_cache: EffectiveTipLruCache(LruMap::new(ByLength::new(
cached_values,
))),
});
Self { provider, oracle_config, cache, ignore_price, inner }
}
/// Returns the configuration of the gas price oracle.
@ -126,11 +139,11 @@ where
.sealed_header_by_number_or_tag(BlockNumberOrTag::Latest)?
.ok_or(EthApiError::UnknownBlockNumber)?;
let mut last_price = self.last_price.lock().await;
let mut inner = self.inner.lock().await;
// if we have stored a last price, then we check whether or not it was for the same head
if last_price.block_hash == header.hash {
return Ok(last_price.price)
if inner.last_price.block_hash == header.hash {
return Ok(inner.last_price.price)
}
// if all responses are empty, then we can return a maximum of 2*check_block blocks' worth
@ -150,13 +163,24 @@ where
};
for _ in 0..max_blocks {
let (parent_hash, block_values) = self
.get_block_values(current_hash, SAMPLE_NUMBER)
.await?
.ok_or(EthApiError::UnknownBlockNumber)?;
// Check if current hash is in cache
let (parent_hash, block_values) =
if let Some(vals) = inner.lowest_effective_tip_cache.get(&current_hash) {
vals.to_owned()
} else {
// Otherwise we fetch it using get_block_values
let (parent_hash, block_values) = self
.get_block_values(current_hash, SAMPLE_NUMBER)
.await?
.ok_or(EthApiError::UnknownBlockNumber)?;
inner
.lowest_effective_tip_cache
.insert(current_hash, (parent_hash, block_values.clone()));
(parent_hash, block_values)
};
if block_values.is_empty() {
results.push(U256::from(last_price.price));
results.push(U256::from(inner.last_price.price));
} else {
results.extend(block_values);
populated_blocks += 1;
@ -171,7 +195,7 @@ where
}
// sort results then take the configured percentile result
let mut price = last_price.price;
let mut price = inner.last_price.price;
if !results.is_empty() {
results.sort_unstable();
price = *results
@ -186,7 +210,7 @@ where
}
}
*last_price = GasPriceOracleResult { block_hash: header.hash, price };
inner.last_price = GasPriceOracleResult { block_hash: header.hash, price };
Ok(price)
}
@ -253,6 +277,26 @@ where
}
}
/// Container type for mutable inner state of the [GasPriceOracle]
#[derive(Debug)]
struct GasPriceOracleInner {
last_price: GasPriceOracleResult,
lowest_effective_tip_cache: EffectiveTipLruCache,
}
/// Wrapper struct for LruMap
#[derive(Deref, DerefMut)]
pub struct EffectiveTipLruCache(LruMap<B256, (B256, Vec<U256>), ByLength>);
impl Debug for EffectiveTipLruCache {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("EffectiveTipLruCache")
.field("cache_length", &self.len())
.field("cache_memory_usage", &self.memory_usage())
.finish()
}
}
/// Stores the last result that the oracle returned
#[derive(Debug, Clone)]
pub struct GasPriceOracleResult {