Adds eth_maxPriorityFeePerGas implementaiton (#570)
* added eth_maxPriorityFeePerGas rpc call * cargo fmt * moved block_base_fee implementation into the trait * added basic test for eth_maxPriorityFeePerGas * added test for eth_maxPriorityFeePerGas calculation * Added support for zero-cost transactions * Added 'eip1559_not_activated' error * Fixes 'chain::supplier::test::return_nodes' test * cargo fmt * cargo fmt * made calculation of fallback priority fee to ignore zero-cost transactions * cargo fmt * made use of 'saturating_sub' instead of minus
This commit is contained in:
parent
64a1614769
commit
3b19a79c37
@ -666,6 +666,20 @@ impl TransactionQueue {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns effective priority fee gas price of currently the worst transaction in the pool.
|
||||
/// If the worst transaction has zero gas price, the minimal gas price is returned.
|
||||
pub fn current_worst_effective_priority_fee(&self) -> U256 {
|
||||
self.pool
|
||||
.read()
|
||||
.worst_transaction()
|
||||
.filter(|tx| !tx.signed().has_zero_gas_price())
|
||||
.map(|tx| {
|
||||
tx.signed()
|
||||
.effective_priority_fee(self.options.read().block_base_fee)
|
||||
})
|
||||
.unwrap_or(self.options.read().minimal_gas_price)
|
||||
}
|
||||
|
||||
/// Returns a status of the queue.
|
||||
pub fn status(&self) -> Status {
|
||||
let pool = self.pool.read();
|
||||
|
@ -1090,6 +1090,10 @@ impl BlockChainClient for TestBlockChainClient {
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn is_aura(&self) -> bool {
|
||||
self.engine().name() == "AuthorityRound"
|
||||
}
|
||||
}
|
||||
|
||||
impl IoClient for TestBlockChainClient {
|
||||
|
@ -416,6 +416,40 @@ pub trait BlockChainClient:
|
||||
corpus.into()
|
||||
}
|
||||
|
||||
/// Sorted list of transaction priority gas prices from at least last sample_size blocks.
|
||||
fn priority_gas_price_corpus(
|
||||
&self,
|
||||
sample_size: usize,
|
||||
eip1559_transition: BlockNumber,
|
||||
) -> ::stats::Corpus<U256> {
|
||||
let mut h = self.chain_info().best_block_hash;
|
||||
let mut corpus = Vec::new();
|
||||
while corpus.is_empty() {
|
||||
for _ in 0..sample_size {
|
||||
let block = match self.block(BlockId::Hash(h)) {
|
||||
Some(block) => block,
|
||||
None => return corpus.into(),
|
||||
};
|
||||
|
||||
if block.number() == 0 || block.number() < eip1559_transition {
|
||||
return corpus.into();
|
||||
}
|
||||
block
|
||||
.transaction_views()
|
||||
.iter()
|
||||
.filter(
|
||||
|t| t.gas_price() > 0.into(), /* filter zero cost transactions */
|
||||
)
|
||||
.foreach(|t| {
|
||||
// As block.number() >= eip_1559_transition, the base_fee should exist
|
||||
corpus.push(t.effective_priority_gas_price(Some(block.header().base_fee())))
|
||||
});
|
||||
h = block.parent_hash().clone();
|
||||
}
|
||||
}
|
||||
corpus.into()
|
||||
}
|
||||
|
||||
/// Get the preferred chain ID to sign on
|
||||
fn signing_chain_id(&self) -> Option<u64>;
|
||||
|
||||
|
@ -1006,6 +1006,14 @@ impl miner::MinerService for Miner {
|
||||
self.transaction_queue.current_worst_gas_price() * 110u32 / 100
|
||||
}
|
||||
|
||||
fn sensible_max_priority_fee(&self) -> U256 {
|
||||
// 10% above our minimum.
|
||||
self.transaction_queue
|
||||
.current_worst_effective_priority_fee()
|
||||
* 110u32
|
||||
/ 100
|
||||
}
|
||||
|
||||
fn sensible_gas_limit(&self) -> U256 {
|
||||
self.params.read().gas_range_target.0 / 5
|
||||
}
|
||||
|
@ -260,6 +260,9 @@ pub trait MinerService: Send + Sync {
|
||||
/// Suggested gas price.
|
||||
fn sensible_gas_price(&self) -> U256;
|
||||
|
||||
/// Suggested max priority fee gas price
|
||||
fn sensible_max_priority_fee(&self) -> U256;
|
||||
|
||||
/// Suggested gas limit.
|
||||
fn sensible_gas_limit(&self) -> U256;
|
||||
|
||||
|
@ -504,7 +504,10 @@ mod test {
|
||||
use super::{super::tests::*, *};
|
||||
use blocks::SyncHeader;
|
||||
use bytes::Bytes;
|
||||
use ethcore::client::{BlockChainClient, EachBlockWith, TestBlockChainClient};
|
||||
use ethcore::{
|
||||
client::{BlockChainClient, EachBlockWith, TestBlockChainClient},
|
||||
spec::Spec,
|
||||
};
|
||||
use ethereum_types::H256;
|
||||
use parking_lot::RwLock;
|
||||
use rlp::{Rlp, RlpStream};
|
||||
@ -769,7 +772,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn return_nodes() {
|
||||
let mut client = TestBlockChainClient::new();
|
||||
let mut client = TestBlockChainClient::new_with_spec(Spec::new_test_round());
|
||||
let queue = RwLock::new(VecDeque::new());
|
||||
let sync = dummy_sync_with_peer(H256::zero(), &client);
|
||||
let ss = TestSnapshotService::new();
|
||||
|
@ -137,6 +137,29 @@ impl<'a> TypedTransactionView<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the actual priority gas price paid to the miner
|
||||
pub fn effective_priority_gas_price(&self, block_base_fee: Option<U256>) -> U256 {
|
||||
match self.transaction_type {
|
||||
TypedTxId::Legacy => self
|
||||
.gas_price()
|
||||
.saturating_sub(block_base_fee.unwrap_or_default()),
|
||||
TypedTxId::AccessList => self
|
||||
.gas_price()
|
||||
.saturating_sub(block_base_fee.unwrap_or_default()),
|
||||
TypedTxId::EIP1559Transaction => {
|
||||
let max_priority_fee_per_gas: U256 =
|
||||
view!(Self, &self.rlp.rlp.data().unwrap()[1..])
|
||||
.rlp
|
||||
.val_at(2);
|
||||
min(
|
||||
max_priority_fee_per_gas,
|
||||
self.gas_price()
|
||||
.saturating_sub(block_base_fee.unwrap_or_default()),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the gas field of the transaction.
|
||||
pub fn gas(&self) -> U256 {
|
||||
match self.transaction_type {
|
||||
@ -260,6 +283,7 @@ mod tests {
|
||||
assert_eq!(view.nonce(), 0.into());
|
||||
assert_eq!(view.gas_price(), 1.into());
|
||||
assert_eq!(view.effective_gas_price(None), 1.into());
|
||||
assert_eq!(view.effective_priority_gas_price(None), 1.into());
|
||||
assert_eq!(view.gas(), 0x61a8.into());
|
||||
assert_eq!(view.value(), 0xa.into());
|
||||
assert_eq!(
|
||||
@ -285,6 +309,7 @@ mod tests {
|
||||
let view = view!(TypedTransactionView, &rlp);
|
||||
assert_eq!(view.nonce(), 0x1.into());
|
||||
assert_eq!(view.gas_price(), 0xa.into());
|
||||
assert_eq!(view.effective_priority_gas_price(None), 0xa.into());
|
||||
assert_eq!(view.gas(), 0x1e241.into());
|
||||
assert_eq!(view.value(), 0x0.into());
|
||||
assert_eq!(view.data(), "".from_hex().unwrap());
|
||||
@ -306,6 +331,10 @@ mod tests {
|
||||
assert_eq!(view.nonce(), 0x1.into());
|
||||
assert_eq!(view.gas_price(), 0xa.into());
|
||||
assert_eq!(view.effective_gas_price(Some(0x07.into())), 0x08.into());
|
||||
assert_eq!(
|
||||
view.effective_priority_gas_price(Some(0x07.into())),
|
||||
0x01.into()
|
||||
);
|
||||
assert_eq!(view.gas(), 0x1e241.into());
|
||||
assert_eq!(view.value(), 0x0.into());
|
||||
assert_eq!(view.data(), "".from_hex().unwrap());
|
||||
|
@ -91,7 +91,10 @@ use ethcore::{client::BlockChainClient, miner::MinerService};
|
||||
use ethereum_types::{Address, H256, H520, U256};
|
||||
use ethkey::Password;
|
||||
use hash::keccak;
|
||||
use types::transaction::{PendingTransaction, SignedTransaction};
|
||||
use types::{
|
||||
transaction::{PendingTransaction, SignedTransaction},
|
||||
BlockNumber,
|
||||
};
|
||||
|
||||
use jsonrpc_core::{
|
||||
futures::{future, Future, IntoFuture},
|
||||
@ -397,6 +400,24 @@ where
|
||||
.unwrap_or_else(|| miner.sensible_gas_price())
|
||||
}
|
||||
|
||||
/// Extract the default priority gas price from a client and miner.
|
||||
pub fn default_max_priority_fee_per_gas<C, M>(
|
||||
client: &C,
|
||||
miner: &M,
|
||||
percentile: usize,
|
||||
eip1559_transition: BlockNumber,
|
||||
) -> U256
|
||||
where
|
||||
C: BlockChainClient,
|
||||
M: MinerService,
|
||||
{
|
||||
client
|
||||
.priority_gas_price_corpus(100, eip1559_transition)
|
||||
.percentile(percentile)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| miner.sensible_max_priority_fee())
|
||||
}
|
||||
|
||||
/// Convert RPC confirmation payload to signer confirmation payload.
|
||||
/// May need to resolve in the future to fetch things like gas price.
|
||||
pub fn from_rpc<D>(
|
||||
|
@ -273,6 +273,10 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eip1559_not_activated() -> Error {
|
||||
unsupported("EIP-1559 is not activated", None)
|
||||
}
|
||||
|
||||
pub fn not_enough_data() -> Error {
|
||||
Error {
|
||||
code: ErrorCode::ServerError(codes::UNSUPPORTED_REQUEST),
|
||||
|
@ -52,7 +52,7 @@ use v1::{
|
||||
self,
|
||||
block_import::is_major_importing,
|
||||
deprecated::{self, DeprecationNotice},
|
||||
dispatch::{default_gas_price, FullDispatcher},
|
||||
dispatch::{default_gas_price, default_max_priority_fee_per_gas, FullDispatcher},
|
||||
errors, fake_sign, limit_logs,
|
||||
},
|
||||
metadata::Metadata,
|
||||
@ -696,6 +696,22 @@ where
|
||||
)))
|
||||
}
|
||||
|
||||
fn max_priority_fee_per_gas(&self) -> BoxFuture<U256> {
|
||||
let latest_block = self.client.chain_info().best_block_number;
|
||||
let eip1559_transition = self.client.engine().params().eip1559_transition;
|
||||
|
||||
if latest_block + 1 >= eip1559_transition {
|
||||
Box::new(future::ok(default_max_priority_fee_per_gas(
|
||||
&*self.client,
|
||||
&*self.miner,
|
||||
self.options.gas_price_percentile,
|
||||
eip1559_transition,
|
||||
)))
|
||||
} else {
|
||||
Box::new(future::done(Err(errors::eip1559_not_activated())))
|
||||
}
|
||||
}
|
||||
|
||||
fn fee_history(
|
||||
&self,
|
||||
mut block_count: U256,
|
||||
@ -805,8 +821,11 @@ where
|
||||
|
||||
gas_and_reward.push((
|
||||
gas_used,
|
||||
txs[i].effective_gas_price(base_fee)
|
||||
- base_fee.unwrap_or_default(),
|
||||
txs[i]
|
||||
.effective_gas_price(base_fee)
|
||||
.saturating_sub(
|
||||
base_fee.unwrap_or_default(),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ use std::{env, sync::Arc};
|
||||
|
||||
use accounts::AccountProvider;
|
||||
use ethcore::{
|
||||
client::{BlockChainClient, ChainInfo, Client, ClientConfig, ImportBlock},
|
||||
client::{BlockChainClient, ChainInfo, Client, ClientConfig, EvmTestClient, ImportBlock},
|
||||
ethereum,
|
||||
miner::Miner,
|
||||
spec::{Genesis, Spec},
|
||||
@ -67,7 +67,7 @@ fn snapshot_service() -> Arc<TestSnapshotService> {
|
||||
|
||||
fn make_spec(chain: &BlockChain) -> Spec {
|
||||
let genesis = Genesis::from(chain.genesis());
|
||||
let mut spec = ethereum::new_frontier_test();
|
||||
let mut spec = EvmTestClient::spec_from_json(&chain.network).unwrap();
|
||||
let state = chain.pre_state.clone().into();
|
||||
spec.set_genesis_state(state)
|
||||
.expect("unable to set genesis state");
|
||||
@ -281,6 +281,26 @@ fn eth_get_block() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eth_get_max_priority_fee_per_gas() {
|
||||
let chain = extract_non_legacy_chain!(
|
||||
"BlockchainTests/ValidBlocks/bcEIP1559/transType",
|
||||
ForkSpec::London
|
||||
);
|
||||
let tester = EthTester::from_chain(&chain);
|
||||
let request = r#"{"method":"eth_maxPriorityFeePerGas","params":[],"id":1,"jsonrpc":"2.0"}"#;
|
||||
|
||||
// We are expecting for 50-th percentile of the previous 100 blocks transactions priority fees.
|
||||
//
|
||||
// Sorted priority fees: 0x64 0x64 0x64 0x7d 0x7d 0xea 0x149.
|
||||
// Currently, the way 50-th percentile is calculated, the 3rd fee would be the result.
|
||||
let response = r#"{"jsonrpc":"2.0","result":"0x64","id":1}"#;
|
||||
assert_eq!(
|
||||
tester.handler.handle_request_sync(request).unwrap(),
|
||||
response
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eth_get_block_by_hash() {
|
||||
let chain = extract_chain!("BlockchainTests/ValidBlocks/bcGasPricerTest/RPC_API_Test");
|
||||
|
@ -359,6 +359,10 @@ impl MinerService for TestMinerService {
|
||||
20_000_000_000u64.into()
|
||||
}
|
||||
|
||||
fn sensible_max_priority_fee(&self) -> U256 {
|
||||
2_000_000_000u64.into()
|
||||
}
|
||||
|
||||
fn sensible_gas_limit(&self) -> U256 {
|
||||
0x5208.into()
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ use std::{
|
||||
|
||||
use accounts::AccountProvider;
|
||||
use ethcore::{
|
||||
client::{BlockChainClient, EachBlockWith, Executed, TestBlockChainClient},
|
||||
client::{BlockChainClient, EachBlockWith, EvmTestClient, Executed, TestBlockChainClient},
|
||||
miner::{self, MinerService},
|
||||
};
|
||||
use ethereum_types::{Address, Bloom, H160, H256, U256};
|
||||
@ -51,6 +51,12 @@ fn blockchain_client() -> Arc<TestBlockChainClient> {
|
||||
Arc::new(client)
|
||||
}
|
||||
|
||||
fn eip1559_blockchain_client() -> Arc<TestBlockChainClient> {
|
||||
let spec = EvmTestClient::spec_from_json(ðjson::spec::ForkSpec::London).unwrap();
|
||||
let client = TestBlockChainClient::new_with_spec(spec);
|
||||
Arc::new(client)
|
||||
}
|
||||
|
||||
fn accounts_provider() -> Arc<AccountProvider> {
|
||||
Arc::new(AccountProvider::transient_provider())
|
||||
}
|
||||
@ -89,8 +95,25 @@ impl Default for EthTester {
|
||||
|
||||
impl EthTester {
|
||||
pub fn new_with_options(options: EthClientOptions) -> Self {
|
||||
let runtime = Runtime::with_thread_count(1);
|
||||
let client = blockchain_client();
|
||||
EthTester::new_with_client_and_options(client, options)
|
||||
}
|
||||
|
||||
fn new_eip1559_with_options(options: EthClientOptions) -> Self {
|
||||
let client = eip1559_blockchain_client();
|
||||
EthTester::new_with_client_and_options(client, options)
|
||||
}
|
||||
|
||||
pub fn add_blocks(&self, count: usize, with: EachBlockWith) {
|
||||
self.client.add_blocks(count, with);
|
||||
self.sync.increase_imported_block_number(count as u64);
|
||||
}
|
||||
|
||||
fn new_with_client_and_options(
|
||||
client: Arc<TestBlockChainClient>,
|
||||
options: EthClientOptions,
|
||||
) -> Self {
|
||||
let runtime = Runtime::with_thread_count(1);
|
||||
let sync = sync_provider();
|
||||
let ap = accounts_provider();
|
||||
let ap2 = ap.clone();
|
||||
@ -126,11 +149,6 @@ impl EthTester {
|
||||
hashrates,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_blocks(&self, count: usize, with: EachBlockWith) {
|
||||
self.client.add_blocks(count, with);
|
||||
self.sync.increase_imported_block_number(count as u64);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -537,6 +555,33 @@ fn rpc_eth_gas_price() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rpc_eth_get_max_priority_fee_per_gas() {
|
||||
let tester = EthTester::new_eip1559_with_options(Default::default());
|
||||
|
||||
let request = r#"{"method":"eth_maxPriorityFeePerGas","params":[],"id":1,"jsonrpc":"2.0"}"#;
|
||||
let response = r#"{"jsonrpc":"2.0","result":"0x77359400","id":1}"#; // 2 GWei
|
||||
|
||||
assert_eq!(
|
||||
tester.io.handle_request_sync(request),
|
||||
Some(response.to_owned())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rpc_eth_get_max_priority_fee_per_gas_error() {
|
||||
let tester = EthTester::default();
|
||||
|
||||
let request = r#"{"method":"eth_maxPriorityFeePerGas","params":[],"id":1,"jsonrpc":"2.0"}"#;
|
||||
let response =
|
||||
r#"{"jsonrpc":"2.0","error":{"code":-32000,"message":"EIP-1559 is not activated"},"id":1}"#;
|
||||
|
||||
assert_eq!(
|
||||
tester.io.handle_request_sync(request),
|
||||
Some(response.to_owned())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rpc_eth_accounts() {
|
||||
let tester = EthTester::default();
|
||||
|
@ -51,6 +51,23 @@ macro_rules! register_test {
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! extract_non_legacy_chain {
|
||||
($file: expr, $network: expr) => {{
|
||||
const RAW_DATA: &'static [u8] = include_bytes!(concat!(
|
||||
"../../../../ethcore/res/json_tests/",
|
||||
$file,
|
||||
".json"
|
||||
));
|
||||
::ethjson::blockchain::Test::load(RAW_DATA)
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.filter(|&(_, ref t)| t.network == $network)
|
||||
.next()
|
||||
.unwrap()
|
||||
.1
|
||||
}};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod eth;
|
||||
#[cfg(test)]
|
||||
|
@ -60,6 +60,10 @@ pub trait Eth {
|
||||
#[rpc(name = "eth_gasPrice")]
|
||||
fn gas_price(&self) -> BoxFuture<U256>;
|
||||
|
||||
/// Returns current max_priority_fee
|
||||
#[rpc(name = "eth_maxPriorityFeePerGas")]
|
||||
fn max_priority_fee_per_gas(&self) -> BoxFuture<U256>;
|
||||
|
||||
/// Returns transaction fee history.
|
||||
#[rpc(name = "eth_feeHistory")]
|
||||
fn fee_history(&self, _: U256, _: BlockNumber, _: Option<Vec<f64>>)
|
||||
|
Loading…
Reference in New Issue
Block a user