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:
Rim Rakhimov 2021-11-25 10:43:00 +03:00 committed by GitHub
parent 64a1614769
commit 3b19a79c37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 244 additions and 15 deletions

View File

@ -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. /// Returns a status of the queue.
pub fn status(&self) -> Status { pub fn status(&self) -> Status {
let pool = self.pool.read(); let pool = self.pool.read();

View File

@ -1090,6 +1090,10 @@ impl BlockChainClient for TestBlockChainClient {
} }
None None
} }
fn is_aura(&self) -> bool {
self.engine().name() == "AuthorityRound"
}
} }
impl IoClient for TestBlockChainClient { impl IoClient for TestBlockChainClient {

View File

@ -416,6 +416,40 @@ pub trait BlockChainClient:
corpus.into() 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 /// Get the preferred chain ID to sign on
fn signing_chain_id(&self) -> Option<u64>; fn signing_chain_id(&self) -> Option<u64>;

View File

@ -1006,6 +1006,14 @@ impl miner::MinerService for Miner {
self.transaction_queue.current_worst_gas_price() * 110u32 / 100 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 { fn sensible_gas_limit(&self) -> U256 {
self.params.read().gas_range_target.0 / 5 self.params.read().gas_range_target.0 / 5
} }

View File

@ -260,6 +260,9 @@ pub trait MinerService: Send + Sync {
/// Suggested gas price. /// Suggested gas price.
fn sensible_gas_price(&self) -> U256; fn sensible_gas_price(&self) -> U256;
/// Suggested max priority fee gas price
fn sensible_max_priority_fee(&self) -> U256;
/// Suggested gas limit. /// Suggested gas limit.
fn sensible_gas_limit(&self) -> U256; fn sensible_gas_limit(&self) -> U256;

View File

@ -504,7 +504,10 @@ mod test {
use super::{super::tests::*, *}; use super::{super::tests::*, *};
use blocks::SyncHeader; use blocks::SyncHeader;
use bytes::Bytes; use bytes::Bytes;
use ethcore::client::{BlockChainClient, EachBlockWith, TestBlockChainClient}; use ethcore::{
client::{BlockChainClient, EachBlockWith, TestBlockChainClient},
spec::Spec,
};
use ethereum_types::H256; use ethereum_types::H256;
use parking_lot::RwLock; use parking_lot::RwLock;
use rlp::{Rlp, RlpStream}; use rlp::{Rlp, RlpStream};
@ -769,7 +772,7 @@ mod test {
#[test] #[test]
fn return_nodes() { 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 queue = RwLock::new(VecDeque::new());
let sync = dummy_sync_with_peer(H256::zero(), &client); let sync = dummy_sync_with_peer(H256::zero(), &client);
let ss = TestSnapshotService::new(); let ss = TestSnapshotService::new();

View File

@ -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. /// Get the gas field of the transaction.
pub fn gas(&self) -> U256 { pub fn gas(&self) -> U256 {
match self.transaction_type { match self.transaction_type {
@ -260,6 +283,7 @@ mod tests {
assert_eq!(view.nonce(), 0.into()); assert_eq!(view.nonce(), 0.into());
assert_eq!(view.gas_price(), 1.into()); assert_eq!(view.gas_price(), 1.into());
assert_eq!(view.effective_gas_price(None), 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.gas(), 0x61a8.into());
assert_eq!(view.value(), 0xa.into()); assert_eq!(view.value(), 0xa.into());
assert_eq!( assert_eq!(
@ -285,6 +309,7 @@ mod tests {
let view = view!(TypedTransactionView, &rlp); let view = view!(TypedTransactionView, &rlp);
assert_eq!(view.nonce(), 0x1.into()); assert_eq!(view.nonce(), 0x1.into());
assert_eq!(view.gas_price(), 0xa.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.gas(), 0x1e241.into());
assert_eq!(view.value(), 0x0.into()); assert_eq!(view.value(), 0x0.into());
assert_eq!(view.data(), "".from_hex().unwrap()); assert_eq!(view.data(), "".from_hex().unwrap());
@ -306,6 +331,10 @@ mod tests {
assert_eq!(view.nonce(), 0x1.into()); assert_eq!(view.nonce(), 0x1.into());
assert_eq!(view.gas_price(), 0xa.into()); assert_eq!(view.gas_price(), 0xa.into());
assert_eq!(view.effective_gas_price(Some(0x07.into())), 0x08.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.gas(), 0x1e241.into());
assert_eq!(view.value(), 0x0.into()); assert_eq!(view.value(), 0x0.into());
assert_eq!(view.data(), "".from_hex().unwrap()); assert_eq!(view.data(), "".from_hex().unwrap());

View File

@ -91,7 +91,10 @@ use ethcore::{client::BlockChainClient, miner::MinerService};
use ethereum_types::{Address, H256, H520, U256}; use ethereum_types::{Address, H256, H520, U256};
use ethkey::Password; use ethkey::Password;
use hash::keccak; use hash::keccak;
use types::transaction::{PendingTransaction, SignedTransaction}; use types::{
transaction::{PendingTransaction, SignedTransaction},
BlockNumber,
};
use jsonrpc_core::{ use jsonrpc_core::{
futures::{future, Future, IntoFuture}, futures::{future, Future, IntoFuture},
@ -397,6 +400,24 @@ where
.unwrap_or_else(|| miner.sensible_gas_price()) .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. /// Convert RPC confirmation payload to signer confirmation payload.
/// May need to resolve in the future to fetch things like gas price. /// May need to resolve in the future to fetch things like gas price.
pub fn from_rpc<D>( pub fn from_rpc<D>(

View File

@ -273,6 +273,10 @@ where
} }
} }
pub fn eip1559_not_activated() -> Error {
unsupported("EIP-1559 is not activated", None)
}
pub fn not_enough_data() -> Error { pub fn not_enough_data() -> Error {
Error { Error {
code: ErrorCode::ServerError(codes::UNSUPPORTED_REQUEST), code: ErrorCode::ServerError(codes::UNSUPPORTED_REQUEST),

View File

@ -52,7 +52,7 @@ use v1::{
self, self,
block_import::is_major_importing, block_import::is_major_importing,
deprecated::{self, DeprecationNotice}, deprecated::{self, DeprecationNotice},
dispatch::{default_gas_price, FullDispatcher}, dispatch::{default_gas_price, default_max_priority_fee_per_gas, FullDispatcher},
errors, fake_sign, limit_logs, errors, fake_sign, limit_logs,
}, },
metadata::Metadata, 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( fn fee_history(
&self, &self,
mut block_count: U256, mut block_count: U256,
@ -805,8 +821,11 @@ where
gas_and_reward.push(( gas_and_reward.push((
gas_used, gas_used,
txs[i].effective_gas_price(base_fee) txs[i]
- base_fee.unwrap_or_default(), .effective_gas_price(base_fee)
.saturating_sub(
base_fee.unwrap_or_default(),
),
)); ));
} }
} }

View File

@ -19,7 +19,7 @@ use std::{env, sync::Arc};
use accounts::AccountProvider; use accounts::AccountProvider;
use ethcore::{ use ethcore::{
client::{BlockChainClient, ChainInfo, Client, ClientConfig, ImportBlock}, client::{BlockChainClient, ChainInfo, Client, ClientConfig, EvmTestClient, ImportBlock},
ethereum, ethereum,
miner::Miner, miner::Miner,
spec::{Genesis, Spec}, spec::{Genesis, Spec},
@ -67,7 +67,7 @@ fn snapshot_service() -> Arc<TestSnapshotService> {
fn make_spec(chain: &BlockChain) -> Spec { fn make_spec(chain: &BlockChain) -> Spec {
let genesis = Genesis::from(chain.genesis()); 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(); let state = chain.pre_state.clone().into();
spec.set_genesis_state(state) spec.set_genesis_state(state)
.expect("unable to set genesis 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] #[test]
fn eth_get_block_by_hash() { fn eth_get_block_by_hash() {
let chain = extract_chain!("BlockchainTests/ValidBlocks/bcGasPricerTest/RPC_API_Test"); let chain = extract_chain!("BlockchainTests/ValidBlocks/bcGasPricerTest/RPC_API_Test");

View File

@ -359,6 +359,10 @@ impl MinerService for TestMinerService {
20_000_000_000u64.into() 20_000_000_000u64.into()
} }
fn sensible_max_priority_fee(&self) -> U256 {
2_000_000_000u64.into()
}
fn sensible_gas_limit(&self) -> U256 { fn sensible_gas_limit(&self) -> U256 {
0x5208.into() 0x5208.into()
} }

View File

@ -23,7 +23,7 @@ use std::{
use accounts::AccountProvider; use accounts::AccountProvider;
use ethcore::{ use ethcore::{
client::{BlockChainClient, EachBlockWith, Executed, TestBlockChainClient}, client::{BlockChainClient, EachBlockWith, EvmTestClient, Executed, TestBlockChainClient},
miner::{self, MinerService}, miner::{self, MinerService},
}; };
use ethereum_types::{Address, Bloom, H160, H256, U256}; use ethereum_types::{Address, Bloom, H160, H256, U256};
@ -51,6 +51,12 @@ fn blockchain_client() -> Arc<TestBlockChainClient> {
Arc::new(client) Arc::new(client)
} }
fn eip1559_blockchain_client() -> Arc<TestBlockChainClient> {
let spec = EvmTestClient::spec_from_json(&ethjson::spec::ForkSpec::London).unwrap();
let client = TestBlockChainClient::new_with_spec(spec);
Arc::new(client)
}
fn accounts_provider() -> Arc<AccountProvider> { fn accounts_provider() -> Arc<AccountProvider> {
Arc::new(AccountProvider::transient_provider()) Arc::new(AccountProvider::transient_provider())
} }
@ -89,8 +95,25 @@ impl Default for EthTester {
impl EthTester { impl EthTester {
pub fn new_with_options(options: EthClientOptions) -> Self { pub fn new_with_options(options: EthClientOptions) -> Self {
let runtime = Runtime::with_thread_count(1);
let client = blockchain_client(); 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 sync = sync_provider();
let ap = accounts_provider(); let ap = accounts_provider();
let ap2 = ap.clone(); let ap2 = ap.clone();
@ -126,11 +149,6 @@ impl EthTester {
hashrates, 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] #[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] #[test]
fn rpc_eth_accounts() { fn rpc_eth_accounts() {
let tester = EthTester::default(); let tester = EthTester::default();

View File

@ -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)] #[cfg(test)]
mod eth; mod eth;
#[cfg(test)] #[cfg(test)]

View File

@ -60,6 +60,10 @@ pub trait Eth {
#[rpc(name = "eth_gasPrice")] #[rpc(name = "eth_gasPrice")]
fn gas_price(&self) -> BoxFuture<U256>; 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. /// Returns transaction fee history.
#[rpc(name = "eth_feeHistory")] #[rpc(name = "eth_feeHistory")]
fn fee_history(&self, _: U256, _: BlockNumber, _: Option<Vec<f64>>) fn fee_history(&self, _: U256, _: BlockNumber, _: Option<Vec<f64>>)