Summary
GasOracle::fetch_params hits the provider on every invocation. In a single block, multiple liquidation opportunities can be queued by the health scanner. Each calls fetch_params independently, making N identical eth_getBlockByNumber(latest) RPC calls that all return the same base fee.
This is unnecessary latency (each call adds one RPC round-trip to opportunity evaluation time) and unnecessary RPC quota consumption.
File
crates/charon-executor/src/gas.rs — GasOracle struct
Fix
Add a block-scoped cache:
pub struct GasOracle {
cache: Mutex<Option<(u64, GasDecision)>>, // (block_number, decision)
}
impl GasOracle {
pub async fn fetch_params(
&self,
provider: &impl Provider,
current_block: u64,
) -> Result<GasDecision, GasError> {
let mut guard = self.cache.lock().await;
if let Some((cached_block, ref decision)) = *guard {
if cached_block == current_block {
return Ok(decision.clone());
}
}
let decision = self.fetch_from_provider(provider).await?;
*guard = Some((current_block, decision.clone()));
Ok(decision)
}
}
The current_block parameter comes from the BlockListener event — the correct design since all per-block work is already block-keyed.
Refs #43
Summary
GasOracle::fetch_paramshits the provider on every invocation. In a single block, multiple liquidation opportunities can be queued by the health scanner. Each callsfetch_paramsindependently, making N identicaleth_getBlockByNumber(latest)RPC calls that all return the same base fee.This is unnecessary latency (each call adds one RPC round-trip to opportunity evaluation time) and unnecessary RPC quota consumption.
File
crates/charon-executor/src/gas.rs—GasOraclestructFix
Add a block-scoped cache:
The
current_blockparameter comes from theBlockListenerevent — the correct design since all per-block work is already block-keyed.Refs #43