diff --git a/crates/concensus/miner/src/pool/queue.rs b/crates/concensus/miner/src/pool/queue.rs index 11ad76050..b00691fd7 100644 --- a/crates/concensus/miner/src/pool/queue.rs +++ b/crates/concensus/miner/src/pool/queue.rs @@ -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(); diff --git a/crates/ethcore/src/client/test_client.rs b/crates/ethcore/src/client/test_client.rs index fdf7bd427..352b242cd 100644 --- a/crates/ethcore/src/client/test_client.rs +++ b/crates/ethcore/src/client/test_client.rs @@ -1090,6 +1090,10 @@ impl BlockChainClient for TestBlockChainClient { } None } + + fn is_aura(&self) -> bool { + self.engine().name() == "AuthorityRound" + } } impl IoClient for TestBlockChainClient { diff --git a/crates/ethcore/src/client/traits.rs b/crates/ethcore/src/client/traits.rs index d3f3abcb2..b9d5e3ad7 100644 --- a/crates/ethcore/src/client/traits.rs +++ b/crates/ethcore/src/client/traits.rs @@ -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 { + 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; diff --git a/crates/ethcore/src/miner/miner.rs b/crates/ethcore/src/miner/miner.rs index 13b5bd671..dfc908176 100644 --- a/crates/ethcore/src/miner/miner.rs +++ b/crates/ethcore/src/miner/miner.rs @@ -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 } diff --git a/crates/ethcore/src/miner/mod.rs b/crates/ethcore/src/miner/mod.rs index eff4320ee..72c0eb999 100644 --- a/crates/ethcore/src/miner/mod.rs +++ b/crates/ethcore/src/miner/mod.rs @@ -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; diff --git a/crates/ethcore/sync/src/chain/supplier.rs b/crates/ethcore/sync/src/chain/supplier.rs index a459365bb..696715a37 100644 --- a/crates/ethcore/sync/src/chain/supplier.rs +++ b/crates/ethcore/sync/src/chain/supplier.rs @@ -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(); diff --git a/crates/ethcore/types/src/views/typed_transaction.rs b/crates/ethcore/types/src/views/typed_transaction.rs index 62c370239..5cdea2194 100644 --- a/crates/ethcore/types/src/views/typed_transaction.rs +++ b/crates/ethcore/types/src/views/typed_transaction.rs @@ -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 { + 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()); diff --git a/crates/rpc/src/v1/helpers/dispatch/mod.rs b/crates/rpc/src/v1/helpers/dispatch/mod.rs index 3d64e4906..cc42e0fda 100644 --- a/crates/rpc/src/v1/helpers/dispatch/mod.rs +++ b/crates/rpc/src/v1/helpers/dispatch/mod.rs @@ -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( + 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( diff --git a/crates/rpc/src/v1/helpers/errors.rs b/crates/rpc/src/v1/helpers/errors.rs index 94c0eaf79..1883a82f0 100644 --- a/crates/rpc/src/v1/helpers/errors.rs +++ b/crates/rpc/src/v1/helpers/errors.rs @@ -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), diff --git a/crates/rpc/src/v1/impls/eth.rs b/crates/rpc/src/v1/impls/eth.rs index c7fdde253..b1733aa2c 100644 --- a/crates/rpc/src/v1/impls/eth.rs +++ b/crates/rpc/src/v1/impls/eth.rs @@ -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 { + 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(), + ), )); } } diff --git a/crates/rpc/src/v1/tests/eth.rs b/crates/rpc/src/v1/tests/eth.rs index ec5ee644b..13e4aeade 100644 --- a/crates/rpc/src/v1/tests/eth.rs +++ b/crates/rpc/src/v1/tests/eth.rs @@ -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 { 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"); diff --git a/crates/rpc/src/v1/tests/helpers/miner_service.rs b/crates/rpc/src/v1/tests/helpers/miner_service.rs index 9930837ab..03fd98741 100644 --- a/crates/rpc/src/v1/tests/helpers/miner_service.rs +++ b/crates/rpc/src/v1/tests/helpers/miner_service.rs @@ -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() } diff --git a/crates/rpc/src/v1/tests/mocked/eth.rs b/crates/rpc/src/v1/tests/mocked/eth.rs index 5e0210414..03a719f1a 100644 --- a/crates/rpc/src/v1/tests/mocked/eth.rs +++ b/crates/rpc/src/v1/tests/mocked/eth.rs @@ -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 { Arc::new(client) } +fn eip1559_blockchain_client() -> Arc { + 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 { 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, + 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(); diff --git a/crates/rpc/src/v1/tests/mod.rs b/crates/rpc/src/v1/tests/mod.rs index 794bbce8a..1da3158ff 100644 --- a/crates/rpc/src/v1/tests/mod.rs +++ b/crates/rpc/src/v1/tests/mod.rs @@ -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)] diff --git a/crates/rpc/src/v1/traits/eth.rs b/crates/rpc/src/v1/traits/eth.rs index e93256c4b..d37d9b51b 100644 --- a/crates/rpc/src/v1/traits/eth.rs +++ b/crates/rpc/src/v1/traits/eth.rs @@ -60,6 +60,10 @@ pub trait Eth { #[rpc(name = "eth_gasPrice")] fn gas_price(&self) -> BoxFuture; + /// Returns current max_priority_fee + #[rpc(name = "eth_maxPriorityFeePerGas")] + fn max_priority_fee_per_gas(&self) -> BoxFuture; + /// Returns transaction fee history. #[rpc(name = "eth_feeHistory")] fn fee_history(&self, _: U256, _: BlockNumber, _: Option>)