referrerpolicy=no-referrer-when-downgrade

pallet_revive_eth_rpc/
fee_history_provider.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17use 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
23/// The size of the fee history cache.
24const 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/// Manages the fee history cache.
34#[derive(Default, Clone)]
35pub struct FeeHistoryProvider {
36	fee_history_cache: Arc<RwLock<BTreeMap<SubstrateBlockNumber, FeeHistoryCacheItem>>>,
37}
38
39impl FeeHistoryProvider {
40	/// Update the fee history cache with the given block and receipts.
41	pub async fn update_fee_history(&self, block: &Block, receipts: &[ReceiptInfo]) {
42		// Evenly spaced percentile list from 0.0 to 100.0 with a 0.5 resolution.
43		// This means we cache 200 percentile points.
44		// Later in request handling we will approximate by rounding percentiles that
45		// fall in between with `(round(n*2)/2)`.
46		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		// Calculate percentile rewards.
67		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	/// Get the fee history for the given block range.
90	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		// Iterate over the requested block range.
119		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 the request includes reward percentiles, get them from the cache.
124				if let Some(ref requested_percentiles) = reward_percentiles {
125					let mut block_rewards = Vec::new();
126					// Resolution is half a point. I.e. 1.0,1.5
127					let resolution_per_percentile: f64 = 2.0;
128					// Get cached reward for each provided percentile.
129					for p in requested_percentiles {
130						// Find the cache index from the user percentile.
131						let p = p.clamp(0.0, 100.0);
132						let index = ((p.round() / 2f64) * 2f64) * resolution_per_percentile;
133						// Get and push the reward.
134						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					// Push block rewards.
142					if !block_rewards.is_empty() {
143						rewards.push(block_rewards);
144					}
145				}
146			}
147		}
148
149		// Next block base fee, use constant value for now
150		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}