diff --git a/crates/rpc/src/v1/impls/eth.rs b/crates/rpc/src/v1/impls/eth.rs index c86df8b76..c7fdde253 100644 --- a/crates/rpc/src/v1/impls/eth.rs +++ b/crates/rpc/src/v1/impls/eth.rs @@ -59,8 +59,8 @@ use v1::{ traits::Eth, types::{ block_number_to_id, Block, BlockNumber, BlockTransactions, Bytes, CallRequest, EthAccount, - Filter, Index, Log, Receipt, RichBlock, StorageProof, SyncInfo, SyncStatus, Transaction, - Work, + EthFeeHistory, Filter, Index, Log, Receipt, RichBlock, StorageProof, SyncInfo, SyncStatus, + Transaction, Work, }, }; @@ -696,6 +696,205 @@ where ))) } + fn fee_history( + &self, + mut block_count: U256, + newest_block: BlockNumber, + reward_percentiles: Option>, + ) -> BoxFuture { + let mut result = EthFeeHistory::default(); + + if block_count < 1.into() { + return Box::new(future::done(Ok(result))); + } + + if block_count > 1024.into() { + block_count = 1024.into(); + } + + let latest_block = self.client.chain_info().best_block_number; + let pending_block = self.client.chain_info().best_block_number + 1; + + let last_block = match newest_block { + BlockNumber::Hash { + hash: _, + require_canonical: _, + } => 0, + BlockNumber::Num(number) => { + if number <= pending_block { + number + } else { + 0 + } + } + BlockNumber::Latest => latest_block, + BlockNumber::Earliest => 0, + BlockNumber::Pending => pending_block, + }; + + let first_block = if last_block >= block_count.as_u64() - 1 { + last_block - (block_count.as_u64() - 1) + } else { + 0 + }; + + result.oldest_block = BlockNumber::Num(first_block); + + let get_block_header = |i| { + self.client + .block_header(BlockId::Number(i)) + .ok_or_else(errors::state_pruned) + .and_then(|h| { + h.decode(self.client.engine().params().eip1559_transition) + .map_err(errors::decode) + }) + }; + + let calculate_base_fee = |h| { + self.client + .engine() + .calculate_base_fee(&h) + .unwrap_or_default() + }; + + let calculate_gas_used_ratio = |h: &Header| { + let gas_used = match self.client.block_receipts(&h.hash()) { + Some(receipts) => receipts + .receipts + .last() + .map_or(U256::zero(), |r| r.gas_used), + None => 0.into(), + }; + + (gas_used.as_u64() as f64) / (h.gas_limit().as_u64() as f64) + }; + + let get_block_transactions = |i| match self.client.block_body(BlockId::Number(i)) { + Some(body) => Some(body.transactions()), + None => None, + }; + + let reward_percentiles = reward_percentiles.unwrap_or_default(); + let mut reward_final = vec![]; + + for i in first_block..=last_block + 1 { + let is_last = i == last_block + 1; + + if i < pending_block { + match get_block_header(i) { + Ok(h) => { + let base_fee = h.base_fee(); + + result.base_fee_per_gas.push(base_fee.unwrap_or_default()); + + if !is_last { + result.gas_used_ratio.push(calculate_gas_used_ratio(&h)); + + if reward_percentiles.len() > 0 { + let mut gas_and_reward: Vec<(U256, U256)> = vec![]; + if let Some(txs) = get_block_transactions(i) { + if let Some(receipt) = self.client.block_receipts(&h.hash()) { + if txs.len() == receipt.receipts.len() { + for i in 0..txs.len() { + let gas_used = if i == 0 { + receipt.receipts[i].gas_used + } else { + receipt.receipts[i].gas_used + - receipt.receipts[i - 1].gas_used + }; + + gas_and_reward.push(( + gas_used, + txs[i].effective_gas_price(base_fee) + - base_fee.unwrap_or_default(), + )); + } + } + } + } + + gas_and_reward.sort_by(|a, b| a.1.cmp(&b.1)); + + reward_final.push( + reward_percentiles + .iter() + .map(|p| { + let target_gas = U256::from( + ((h.gas_used().as_u64() as f64) * p / 100.0) as u64, + ); + let mut sum_gas = U256::default(); + for pair in &gas_and_reward { + sum_gas += pair.0; + if target_gas <= sum_gas { + return pair.1; + } + } + 0.into() + }) + .collect(), + ); + } + } + } + Err(_) => break, //reorg happened, skip rest of the blocks + } + } else if i == pending_block { + match self.miner.pending_block_header(i - 1) { + Some(h) => { + result + .base_fee_per_gas + .push(h.base_fee().unwrap_or_default()); + + if !is_last { + result.gas_used_ratio.push(calculate_gas_used_ratio(&h)); + + if reward_percentiles.len() > 0 { + //zero values since can't be calculated for pending block + reward_final.push(vec![0.into(); reward_percentiles.len()]); + } + } + } + None => { + //calculate base fee based on the latest block + match get_block_header(i - 1) { + Ok(h) => { + result.base_fee_per_gas.push(calculate_base_fee(h)); + + if !is_last { + result.gas_used_ratio.push(0.into()); + + if reward_percentiles.len() > 0 { + //zero values since can't be calculated for pending block + reward_final.push(vec![0.into(); reward_percentiles.len()]); + } + } + } + Err(_) => break, //reorg happened, skip rest of the blocks + } + } + } + } else if i == pending_block + 1 { + //calculate base fee based on the pending block, if exist + match self.miner.pending_block_header(i - 1) { + Some(h) => { + result.base_fee_per_gas.push(calculate_base_fee(h)); + } + None => { + result.base_fee_per_gas.push(0.into()); + } + } + } else { + unreachable!(); + }; + } + + if !reward_final.is_empty() { + result.reward = Some(reward_final); + } + + Box::new(future::done(Ok(result))) + } + fn accounts(&self) -> Result> { self.deprecation_notice .print("eth_accounts", deprecated::msgs::ACCOUNTS); diff --git a/crates/rpc/src/v1/traits/eth.rs b/crates/rpc/src/v1/traits/eth.rs index 86b60c0bd..e93256c4b 100644 --- a/crates/rpc/src/v1/traits/eth.rs +++ b/crates/rpc/src/v1/traits/eth.rs @@ -20,8 +20,8 @@ use jsonrpc_core::{BoxFuture, Result}; use jsonrpc_derive::rpc; use v1::types::{ - BlockNumber, Bytes, CallRequest, EthAccount, Filter, FilterChanges, Index, Log, Receipt, - RichBlock, SyncStatus, Transaction, Work, + BlockNumber, Bytes, CallRequest, EthAccount, EthFeeHistory, Filter, FilterChanges, Index, Log, + Receipt, RichBlock, SyncStatus, Transaction, Work, }; /// Eth rpc interface. @@ -60,6 +60,11 @@ pub trait Eth { #[rpc(name = "eth_gasPrice")] fn gas_price(&self) -> BoxFuture; + /// Returns transaction fee history. + #[rpc(name = "eth_feeHistory")] + fn fee_history(&self, _: U256, _: BlockNumber, _: Option>) + -> BoxFuture; + /// Returns accounts list. #[rpc(name = "eth_accounts")] fn accounts(&self) -> Result>; diff --git a/crates/rpc/src/v1/types/fee_history.rs b/crates/rpc/src/v1/types/fee_history.rs new file mode 100644 index 000000000..6c7eaf865 --- /dev/null +++ b/crates/rpc/src/v1/types/fee_history.rs @@ -0,0 +1,30 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of OpenEthereum. + +// OpenEthereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// OpenEthereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with OpenEthereum. If not, see . + +//! Return types for RPC calls + +use ethereum_types::U256; +use v1::types::BlockNumber; + +/// Account information. +#[derive(Debug, Default, Clone, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct EthFeeHistory { + pub oldest_block: BlockNumber, + pub base_fee_per_gas: Vec, + pub gas_used_ratio: Vec, + pub reward: Option>>, +} diff --git a/crates/rpc/src/v1/types/mod.rs b/crates/rpc/src/v1/types/mod.rs index 3b8138dce..8eb7bbe5e 100644 --- a/crates/rpc/src/v1/types/mod.rs +++ b/crates/rpc/src/v1/types/mod.rs @@ -29,6 +29,7 @@ pub use self::{ }, derivation::{Derive, DeriveHash, DeriveHierarchical}, eip191::{EIP191Version, PresignedTransaction}, + fee_history::EthFeeHistory, filter::{Filter, FilterChanges}, histogram::Histogram, index::Index, @@ -62,6 +63,7 @@ mod call_request; mod confirmations; mod derivation; mod eip191; +mod fee_history; mod filter; mod histogram; mod index;