diff --git a/Cargo.lock b/Cargo.lock index b092d6c1c..e30f53c7d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6184,6 +6184,7 @@ dependencies = [ "assert_matches", "async-trait", "bytes", + "derive_more", "futures", "http", "http-body", diff --git a/crates/rpc/rpc/Cargo.toml b/crates/rpc/rpc/Cargo.toml index 6ca1110a3..3f9bcbcf3 100644 --- a/crates/rpc/rpc/Cargo.toml +++ b/crates/rpc/rpc/Cargo.toml @@ -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"] } diff --git a/crates/rpc/rpc/src/eth/gas_oracle.rs b/crates/rpc/rpc/src/eth/gas_oracle.rs index 9c130c39c..1fbd18741 100644 --- a/crates/rpc/rpc/src/eth/gas_oracle.rs +++ b/crates/rpc/rpc/src/eth/gas_oracle.rs @@ -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 { oracle_config: GasPriceOracleConfig, /// The price under which the sample will be ignored. ignore_price: Option, - /// The latest calculated price and its block hash - last_price: Mutex, + /// Stores the latest calculated price and its block hash and Cache stores the lowest effective + /// tip values of recent blocks + inner: Mutex, } impl GasPriceOracle @@ -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(¤t_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), 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 {