pallet_revive_eth_rpc/
fee_history_provider.rs1use crate::{client::SubstrateBlockNumber, ClientError};
18use pallet_revive::evm::{Block, FeeHistoryResult, ReceiptInfo};
19use sp_core::U256;
20use std::{collections::BTreeMap, sync::Arc};
21use tokio::sync::RwLock;
22
23const CACHE_SIZE: u32 = 1024;
25
26#[derive(Default, Clone)]
27struct FeeHistoryCacheItem {
28 base_fee: u128,
29 gas_used_ratio: f64,
30 rewards: Vec<u128>,
31}
32
33#[derive(Default, Clone)]
35pub struct FeeHistoryProvider {
36 fee_history_cache: Arc<RwLock<BTreeMap<SubstrateBlockNumber, FeeHistoryCacheItem>>>,
37}
38
39impl FeeHistoryProvider {
40 pub async fn update_fee_history(&self, block: &Block, receipts: &[ReceiptInfo]) {
42 let reward_percentiles: Vec<f64> = (0..=200).map(|i| i as f64 * 0.5).collect();
47 let block_number: SubstrateBlockNumber =
48 block.number.try_into().expect("Block number is always valid");
49
50 let base_fee = block.base_fee_per_gas.as_u128();
51 let gas_used = block.gas_used.as_u128();
52 let gas_used_ratio = (gas_used as f64) / (block.gas_limit.as_u128() as f64);
53 let mut result = FeeHistoryCacheItem { base_fee, gas_used_ratio, rewards: vec![] };
54
55 let mut receipts = receipts
56 .iter()
57 .map(|receipt| {
58 let gas_used = receipt.gas_used.as_u128();
59 let effective_reward =
60 receipt.effective_gas_price.as_u128().saturating_sub(base_fee);
61 (gas_used, effective_reward)
62 })
63 .collect::<Vec<_>>();
64 receipts.sort_by(|(_, a), (_, b)| a.cmp(b));
65
66 result.rewards = reward_percentiles
68 .into_iter()
69 .filter_map(|p| {
70 let target_gas = (p * gas_used as f64 / 100f64) as u128;
71 let mut sum_gas = 0u128;
72 for (gas_used, reward) in &receipts {
73 sum_gas += gas_used;
74 if target_gas <= sum_gas {
75 return Some(*reward);
76 }
77 }
78 None
79 })
80 .collect();
81
82 let mut cache = self.fee_history_cache.write().await;
83 if cache.len() >= CACHE_SIZE as usize {
84 cache.pop_first();
85 }
86 cache.insert(block_number, result);
87 }
88
89 pub async fn fee_history(
91 &self,
92 block_count: u32,
93 highest: SubstrateBlockNumber,
94 reward_percentiles: Option<Vec<f64>>,
95 ) -> Result<FeeHistoryResult, ClientError> {
96 let block_count = block_count.min(CACHE_SIZE);
97
98 let cache = self.fee_history_cache.read().await;
99 let Some(lowest_in_cache) = cache.first_key_value().map(|(k, _)| *k) else {
100 return Ok(FeeHistoryResult {
101 oldest_block: U256::zero(),
102 base_fee_per_gas: vec![],
103 gas_used_ratio: vec![],
104 reward: vec![],
105 })
106 };
107
108 let lowest = highest.saturating_sub(block_count.saturating_sub(1)).max(lowest_in_cache);
109
110 let mut response = FeeHistoryResult {
111 oldest_block: U256::from(lowest),
112 base_fee_per_gas: Vec::new(),
113 gas_used_ratio: Vec::new(),
114 reward: Default::default(),
115 };
116
117 let rewards = &mut response.reward;
118 for n in lowest..=highest {
120 if let Some(block) = cache.get(&n) {
121 response.base_fee_per_gas.push(U256::from(block.base_fee));
122 response.gas_used_ratio.push(block.gas_used_ratio);
123 if let Some(ref requested_percentiles) = reward_percentiles {
125 let mut block_rewards = Vec::new();
126 let resolution_per_percentile: f64 = 2.0;
128 for p in requested_percentiles {
130 let p = p.clamp(0.0, 100.0);
132 let index = ((p.round() / 2f64) * 2f64) * resolution_per_percentile;
133 let reward = if let Some(r) = block.rewards.get(index as usize) {
135 U256::from(*r)
136 } else {
137 U256::zero()
138 };
139 block_rewards.push(reward);
140 }
141 if !block_rewards.is_empty() {
143 rewards.push(block_rewards);
144 }
145 }
146 }
147 }
148
149 let base_fee = cache
151 .last_key_value()
152 .map(|(_, block)| U256::from(block.base_fee))
153 .unwrap_or_default();
154 response.base_fee_per_gas.push(base_fee);
155 Ok(response)
156 }
157}
158
159#[tokio::test]
160async fn test_update_fee_history() {
161 let block = Block {
162 number: U256::from(200u64),
163 base_fee_per_gas: U256::from(1000u64),
164 gas_used: U256::from(600u64),
165 gas_limit: U256::from(1200u64),
166 ..Default::default()
167 };
168
169 let receipts = vec![
170 ReceiptInfo {
171 gas_used: U256::from(200u64),
172 effective_gas_price: U256::from(1200u64),
173 ..Default::default()
174 },
175 ReceiptInfo {
176 gas_used: U256::from(200u64),
177 effective_gas_price: U256::from(1100u64),
178 ..Default::default()
179 },
180 ReceiptInfo {
181 gas_used: U256::from(200u64),
182 effective_gas_price: U256::from(1050u64),
183 ..Default::default()
184 },
185 ];
186
187 let provider = FeeHistoryProvider { fee_history_cache: Arc::new(RwLock::new(BTreeMap::new())) };
188 provider.update_fee_history(&block, &receipts).await;
189
190 let fee_history_result =
191 provider.fee_history(1, 200, Some(vec![0.0f64, 50.0, 100.0])).await.unwrap();
192
193 let expected_result = FeeHistoryResult {
194 oldest_block: U256::from(200),
195 base_fee_per_gas: vec![U256::from(1000), U256::from(1000)],
196 gas_used_ratio: vec![0.5f64],
197 reward: vec![vec![U256::from(50), U256::from(100), U256::from(200)]],
198 };
199 assert_eq!(fee_history_result, expected_result);
200}