fc_rpc/eth/
fee.rs

1// This file is part of Frontier.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19use ethereum_types::U256;
20use jsonrpsee::core::RpcResult;
21// Substrate
22use sc_client_api::backend::{Backend, StorageProvider};
23use sp_api::ProvideRuntimeApi;
24use sp_blockchain::HeaderBackend;
25use sp_runtime::{
26	traits::{Block as BlockT, UniqueSaturatedInto},
27	Permill,
28};
29// Frontier
30use fc_rpc_core::types::*;
31use fp_rpc::EthereumRuntimeRPCApi;
32
33use crate::{eth::Eth, frontier_backend_client, internal_err};
34
35impl<B, C, P, CT, BE, CIDP, EC> Eth<B, C, P, CT, BE, CIDP, EC>
36where
37	B: BlockT,
38	C: ProvideRuntimeApi<B>,
39	C::Api: EthereumRuntimeRPCApi<B>,
40	C: HeaderBackend<B> + StorageProvider<B, BE> + 'static,
41	BE: Backend<B> + 'static,
42{
43	pub fn gas_price(&self) -> RpcResult<U256> {
44		let block_hash = self.client.info().best_hash;
45
46		self.client
47			.runtime_api()
48			.gas_price(block_hash)
49			.map_err(|err| internal_err(format!("fetch runtime chain id failed: {err:?}")))
50	}
51
52	pub async fn fee_history(
53		&self,
54		block_count: u64,
55		newest_block: BlockNumberOrHash,
56		reward_percentiles: Option<Vec<f64>>,
57	) -> RpcResult<FeeHistory> {
58		// The max supported range size is 1024 by spec.
59		let range_limit: u64 = 1024;
60		let block_count: u64 = u64::min(block_count, range_limit);
61
62		if let Some(id) = frontier_backend_client::native_block_id::<B, C>(
63			self.client.as_ref(),
64			self.backend.as_ref(),
65			Some(newest_block),
66		)
67		.await?
68		{
69			let Ok(number) = self.client.expect_block_number_from_id(&id) else {
70				return Err(internal_err(format!(
71					"Failed to retrieve block number at {id}"
72				)));
73			};
74			// Highest and lowest block number within the requested range.
75			let highest = UniqueSaturatedInto::<u64>::unique_saturated_into(number);
76			let lowest = highest.saturating_sub(block_count.saturating_sub(1));
77			// Tip of the chain.
78			let best_number =
79				UniqueSaturatedInto::<u64>::unique_saturated_into(self.client.info().best_number);
80			// Only support in-cache queries.
81			if lowest < best_number.saturating_sub(self.fee_history_cache_limit) {
82				return Err(internal_err("Block range out of bounds."));
83			}
84			if let Ok(fee_history_cache) = &self.fee_history_cache.lock() {
85				let mut response = FeeHistory {
86					oldest_block: U256::from(lowest),
87					base_fee_per_gas: Vec::new(),
88					gas_used_ratio: Vec::new(),
89					reward: None,
90				};
91				let mut rewards = Vec::new();
92				// Iterate over the requested block range.
93				for n in lowest..highest + 1 {
94					if let Some(block) = fee_history_cache.get(&n) {
95						response.base_fee_per_gas.push(U256::from(block.base_fee));
96						response.gas_used_ratio.push(block.gas_used_ratio);
97						// If the request includes reward percentiles, get them from the cache.
98						if let Some(ref requested_percentiles) = reward_percentiles {
99							let mut block_rewards = Vec::new();
100							// Resolution is half a point. I.e. 1.0,1.5
101							let resolution_per_percentile: f64 = 2.0;
102							// Get cached reward for each provided percentile.
103							for p in requested_percentiles {
104								// Find the cache index from the user percentile.
105								let p = p.clamp(0.0, 100.0);
106								let index = ((p.round() / 2f64) * 2f64) * resolution_per_percentile;
107								// Get and push the reward.
108								let reward = if let Some(r) = block.rewards.get(index as usize) {
109									U256::from(*r)
110								} else {
111									U256::zero()
112								};
113								block_rewards.push(reward);
114							}
115							// Push block rewards.
116							if !block_rewards.is_empty() {
117								// Push block rewards.
118								rewards.push(block_rewards);
119							}
120						}
121					}
122				}
123				if rewards.len() > 0 {
124					response.reward = Some(rewards);
125				}
126				// Calculate next base fee.
127				if let (Some(last_gas_used), Some(last_fee_per_gas)) = (
128					response.gas_used_ratio.last(),
129					response.base_fee_per_gas.last(),
130				) {
131					let substrate_hash = self
132						.client
133						.expect_block_hash_from_id(&id)
134						.map_err(|_| internal_err(format!("Expect block number from id: {id}")))?;
135					let elasticity = self
136						.storage_override
137						.elasticity(substrate_hash)
138						.unwrap_or(Permill::from_parts(125_000))
139						.deconstruct();
140					let elasticity = elasticity as f64 / 1_000_000f64;
141					let last_fee_per_gas =
142						UniqueSaturatedInto::<u64>::unique_saturated_into(*last_fee_per_gas) as f64;
143					if last_gas_used > &0.5 {
144						// Increase base gas
145						let increase = ((last_gas_used - 0.5) * 2f64) * elasticity;
146						let new_base_fee =
147							(last_fee_per_gas + (last_fee_per_gas * increase)) as u64;
148						response.base_fee_per_gas.push(U256::from(new_base_fee));
149					} else if last_gas_used < &0.5 {
150						// Decrease base gas
151						let increase = ((0.5 - last_gas_used) * 2f64) * elasticity;
152						let new_base_fee =
153							(last_fee_per_gas - (last_fee_per_gas * increase)) as u64;
154						response.base_fee_per_gas.push(U256::from(new_base_fee));
155					} else {
156						// Same base gas
157						response
158							.base_fee_per_gas
159							.push(U256::from(last_fee_per_gas as u64));
160					}
161				}
162				return Ok(response);
163			} else {
164				return Err(internal_err("Failed to read fee history cache."));
165			}
166		}
167		Err(internal_err(format!(
168			"Failed to retrieve requested block {newest_block:?}."
169		)))
170	}
171
172	pub fn max_priority_fee_per_gas(&self) -> RpcResult<U256> {
173		// https://github.com/ethereum/go-ethereum/blob/master/eth/ethconfig/config.go#L44-L51
174		let at_percentile = 60;
175		let block_count = 20;
176		let index = (at_percentile * 2) as usize;
177
178		let highest =
179			UniqueSaturatedInto::<u64>::unique_saturated_into(self.client.info().best_number);
180		let lowest = highest.saturating_sub(block_count - 1);
181
182		// https://github.com/ethereum/go-ethereum/blob/master/eth/gasprice/gasprice.go#L149
183		let mut rewards = Vec::new();
184		if let Ok(fee_history_cache) = &self.fee_history_cache.lock() {
185			for n in lowest..highest + 1 {
186				if let Some(block) = fee_history_cache.get(&n) {
187					let reward = if let Some(r) = block.rewards.get(index) {
188						U256::from(*r)
189					} else {
190						U256::zero()
191					};
192					rewards.push(reward);
193				}
194			}
195		} else {
196			return Err(internal_err("Failed to read fee oracle cache."));
197		}
198		Ok(*rewards.iter().min().unwrap_or(&U256::zero()))
199	}
200}