Skip to content
This repository was archived by the owner on Nov 6, 2020. It is now read-only.

Commit 8b169ec

Browse files
tomusdrwandresilva
authored andcommitted
Minimal effective gas price in the queue (#8934)
* Minimal effective gas price. * Fix naming, add test * Fix minimal entry score and add test. * Fix worst_transaction. * Remove effective gas price threshold. * Don't leak gas_price decisions out of Scoring.
1 parent 768d15c commit 8b169ec

File tree

6 files changed

+176
-39
lines changed

6 files changed

+176
-39
lines changed

miner/src/pool/queue.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,20 @@ impl TransactionQueue {
176176
let _timer = ::trace_time::PerfTimer::new("pool::verify_and_import");
177177
let options = self.options.read().clone();
178178

179-
let verifier = verifier::Verifier::new(client, options, self.insertion_id.clone());
179+
let transaction_to_replace = {
180+
let pool = self.pool.read();
181+
if pool.is_full() {
182+
pool.worst_transaction().map(|worst| (pool.scoring().clone(), worst))
183+
} else {
184+
None
185+
}
186+
};
187+
let verifier = verifier::Verifier::new(
188+
client,
189+
options,
190+
self.insertion_id.clone(),
191+
transaction_to_replace,
192+
);
180193
let results = transactions
181194
.into_iter()
182195
.map(|transaction| {

miner/src/pool/scoring.rs

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,42 @@ use std::cmp;
3131

3232
use ethereum_types::U256;
3333
use txpool;
34-
use super::{PrioritizationStrategy, VerifiedTransaction};
34+
use super::{verifier, PrioritizationStrategy, VerifiedTransaction};
3535

3636
/// Transaction with the same (sender, nonce) can be replaced only if
3737
/// `new_gas_price > old_gas_price + old_gas_price >> SHIFT`
3838
const GAS_PRICE_BUMP_SHIFT: usize = 3; // 2 = 25%, 3 = 12.5%, 4 = 6.25%
3939

40+
/// Calculate minimal gas price requirement.
41+
#[inline]
42+
fn bump_gas_price(old_gp: U256) -> U256 {
43+
old_gp.saturating_add(old_gp >> GAS_PRICE_BUMP_SHIFT)
44+
}
45+
4046
/// Simple, gas-price based scoring for transactions.
4147
///
4248
/// NOTE: Currently penalization does not apply to new transactions that enter the pool.
4349
/// We might want to store penalization status in some persistent state.
44-
#[derive(Debug)]
50+
#[derive(Debug, Clone)]
4551
pub struct NonceAndGasPrice(pub PrioritizationStrategy);
4652

53+
impl NonceAndGasPrice {
54+
/// Decide if the transaction should even be considered into the pool (if the pool is full).
55+
///
56+
/// Used by Verifier to quickly reject transactions that don't have any chance to get into the pool later on,
57+
/// and save time on more expensive checks like sender recovery, etc.
58+
///
59+
/// NOTE The method is never called for zero-gas-price transactions or local transactions
60+
/// (such transactions are always considered to the pool and potentially rejected later on)
61+
pub fn should_reject_early(&self, old: &VerifiedTransaction, new: &verifier::Transaction) -> bool {
62+
if old.priority().is_local() {
63+
return true
64+
}
65+
66+
&old.transaction.gas_price > new.gas_price()
67+
}
68+
}
69+
4770
impl txpool::Scoring<VerifiedTransaction> for NonceAndGasPrice {
4871
type Score = U256;
4972
type Event = ();
@@ -60,7 +83,7 @@ impl txpool::Scoring<VerifiedTransaction> for NonceAndGasPrice {
6083
let old_gp = old.transaction.gas_price;
6184
let new_gp = new.transaction.gas_price;
6285

63-
let min_required_gp = old_gp + (old_gp >> GAS_PRICE_BUMP_SHIFT);
86+
let min_required_gp = bump_gas_price(old_gp);
6487

6588
match min_required_gp.cmp(&new_gp) {
6689
cmp::Ordering::Greater => txpool::scoring::Choice::RejectNew,

miner/src/pool/tests/mod.rs

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -815,8 +815,8 @@ fn should_avoid_verifying_transaction_already_in_pool() {
815815
},
816816
PrioritizationStrategy::GasPriceOnly,
817817
);
818-
let client = TestClient::new();
819-
let tx1 = Tx::default().signed().unverified();
818+
let client = TestClient::new().with_balance(1_000_000_000);
819+
let tx1 = Tx::gas_price(2).signed().unverified();
820820

821821
let res = txq.import(client.clone(), vec![tx1.clone()]);
822822
assert_eq!(res, vec![Ok(())]);
@@ -832,3 +832,41 @@ fn should_avoid_verifying_transaction_already_in_pool() {
832832
// then
833833
assert_eq!(txq.status().status.transaction_count, 1);
834834
}
835+
836+
#[test]
837+
fn should_reject_early_in_case_gas_price_is_less_than_min_effective() {
838+
// given
839+
let txq = TransactionQueue::new(
840+
txpool::Options {
841+
max_count: 1,
842+
max_per_sender: 2,
843+
max_mem_usage: 50
844+
},
845+
verifier::Options {
846+
minimal_gas_price: 1.into(),
847+
block_gas_limit: 1_000_000.into(),
848+
tx_gas_limit: 1_000_000.into(),
849+
},
850+
PrioritizationStrategy::GasPriceOnly,
851+
);
852+
let client = TestClient::new().with_balance(1_000_000_000);
853+
let tx1 = Tx::gas_price(2).signed().unverified();
854+
855+
let res = txq.import(client.clone(), vec![tx1]);
856+
assert_eq!(res, vec![Ok(())]);
857+
assert_eq!(txq.status().status.transaction_count, 1);
858+
assert!(client.was_verification_triggered());
859+
860+
// when
861+
let client = TestClient::new();
862+
let tx1 = Tx::default().signed().unverified();
863+
let res = txq.import(client.clone(), vec![tx1]);
864+
assert_eq!(res, vec![Err(transaction::Error::InsufficientGasPrice {
865+
minimal: 2.into(),
866+
got: 1.into(),
867+
})]);
868+
assert!(!client.was_verification_triggered());
869+
870+
// then
871+
assert_eq!(txq.status().status.transaction_count, 1);
872+
}

miner/src/pool/verifier.rs

Lines changed: 56 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -85,20 +85,20 @@ impl Transaction {
8585
}
8686
}
8787

88-
fn gas(&self) -> &U256 {
88+
/// Return transaction gas price
89+
pub fn gas_price(&self) -> &U256 {
8990
match *self {
90-
Transaction::Unverified(ref tx) => &tx.gas,
91-
Transaction::Retracted(ref tx) => &tx.gas,
92-
Transaction::Local(ref tx) => &tx.gas,
91+
Transaction::Unverified(ref tx) => &tx.gas_price,
92+
Transaction::Retracted(ref tx) => &tx.gas_price,
93+
Transaction::Local(ref tx) => &tx.gas_price,
9394
}
9495
}
9596

96-
97-
fn gas_price(&self) -> &U256 {
97+
fn gas(&self) -> &U256 {
9898
match *self {
99-
Transaction::Unverified(ref tx) => &tx.gas_price,
100-
Transaction::Retracted(ref tx) => &tx.gas_price,
101-
Transaction::Local(ref tx) => &tx.gas_price,
99+
Transaction::Unverified(ref tx) => &tx.gas,
100+
Transaction::Retracted(ref tx) => &tx.gas,
101+
Transaction::Local(ref tx) => &tx.gas,
102102
}
103103
}
104104

@@ -129,24 +129,31 @@ impl Transaction {
129129
///
130130
/// Verification can be run in parallel for all incoming transactions.
131131
#[derive(Debug)]
132-
pub struct Verifier<C> {
132+
pub struct Verifier<C, S, V> {
133133
client: C,
134134
options: Options,
135135
id: Arc<AtomicUsize>,
136+
transaction_to_replace: Option<(S, Arc<V>)>,
136137
}
137138

138-
impl<C> Verifier<C> {
139+
impl<C, S, V> Verifier<C, S, V> {
139140
/// Creates new transaction verfier with specified options.
140-
pub fn new(client: C, options: Options, id: Arc<AtomicUsize>) -> Self {
141+
pub fn new(
142+
client: C,
143+
options: Options,
144+
id: Arc<AtomicUsize>,
145+
transaction_to_replace: Option<(S, Arc<V>)>,
146+
) -> Self {
141147
Verifier {
142148
client,
143149
options,
144150
id,
151+
transaction_to_replace,
145152
}
146153
}
147154
}
148155

149-
impl<C: Client> txpool::Verifier<Transaction> for Verifier<C> {
156+
impl<C: Client> txpool::Verifier<Transaction> for Verifier<C, ::pool::scoring::NonceAndGasPrice, VerifiedTransaction> {
150157
type Error = transaction::Error;
151158
type VerifiedTransaction = VerifiedTransaction;
152159

@@ -165,7 +172,7 @@ impl<C: Client> txpool::Verifier<Transaction> for Verifier<C> {
165172
if tx.gas() > &gas_limit {
166173
debug!(
167174
target: "txqueue",
168-
"[{:?}] Dropping transaction above gas limit: {} > min({}, {})",
175+
"[{:?}] Rejected transaction above gas limit: {} > min({}, {})",
169176
hash,
170177
tx.gas(),
171178
self.options.block_gas_limit,
@@ -180,7 +187,7 @@ impl<C: Client> txpool::Verifier<Transaction> for Verifier<C> {
180187
let minimal_gas = self.client.required_gas(tx.transaction());
181188
if tx.gas() < &minimal_gas {
182189
trace!(target: "txqueue",
183-
"[{:?}] Dropping transaction with insufficient gas: {} < {}",
190+
"[{:?}] Rejected transaction with insufficient gas: {} < {}",
184191
hash,
185192
tx.gas(),
186193
minimal_gas,
@@ -193,22 +200,40 @@ impl<C: Client> txpool::Verifier<Transaction> for Verifier<C> {
193200
}
194201

195202
let is_own = tx.is_local();
196-
// Quick exit for non-service transactions
197-
if tx.gas_price() < &self.options.minimal_gas_price
198-
&& !tx.gas_price().is_zero()
199-
&& !is_own
200-
{
201-
trace!(
202-
target: "txqueue",
203-
"[{:?}] Rejected tx below minimal gas price threshold: {} < {}",
204-
hash,
205-
tx.gas_price(),
206-
self.options.minimal_gas_price,
207-
);
208-
bail!(transaction::Error::InsufficientGasPrice {
209-
minimal: self.options.minimal_gas_price,
210-
got: *tx.gas_price(),
211-
});
203+
// Quick exit for non-service and non-local transactions
204+
//
205+
// We're checking if the transaction is below configured minimal gas price
206+
// or the effective minimal gas price in case the pool is full.
207+
if !tx.gas_price().is_zero() && !is_own {
208+
if tx.gas_price() < &self.options.minimal_gas_price {
209+
trace!(
210+
target: "txqueue",
211+
"[{:?}] Rejected tx below minimal gas price threshold: {} < {}",
212+
hash,
213+
tx.gas_price(),
214+
self.options.minimal_gas_price,
215+
);
216+
bail!(transaction::Error::InsufficientGasPrice {
217+
minimal: self.options.minimal_gas_price,
218+
got: *tx.gas_price(),
219+
});
220+
}
221+
222+
if let Some((ref scoring, ref vtx)) = self.transaction_to_replace {
223+
if scoring.should_reject_early(vtx, &tx) {
224+
trace!(
225+
target: "txqueue",
226+
"[{:?}] Rejected tx early, cause it doesn't have any chance to get to the pool: (gas price: {} < {})",
227+
hash,
228+
tx.gas_price(),
229+
vtx.transaction.gas_price,
230+
);
231+
bail!(transaction::Error::InsufficientGasPrice {
232+
minimal: vtx.transaction.gas_price,
233+
got: *tx.gas_price(),
234+
});
235+
}
236+
}
212237
}
213238

214239
// Some more heavy checks below.

transaction-pool/src/pool.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,13 @@ impl<T, S, L> Pool<T, S, L> where
390390

391391
/// Returns worst transaction in the queue (if any).
392392
pub fn worst_transaction(&self) -> Option<Arc<T>> {
393-
self.worst_transactions.iter().next().map(|x| x.transaction.transaction.clone())
393+
self.worst_transactions.iter().next_back().map(|x| x.transaction.transaction.clone())
394+
}
395+
396+
/// Returns true if the pool is at it's capacity.
397+
pub fn is_full(&self) -> bool {
398+
self.by_hash.len() >= self.options.max_count
399+
|| self.mem_usage >= self.options.max_mem_usage
394400
}
395401

396402
/// Returns an iterator of pending (ready) transactions.
@@ -477,6 +483,11 @@ impl<T, S, L> Pool<T, S, L> where
477483
&self.listener
478484
}
479485

486+
/// Borrows scoring instance.
487+
pub fn scoring(&self) -> &S {
488+
&self.scoring
489+
}
490+
480491
/// Borrows listener mutably.
481492
pub fn listener_mut(&mut self) -> &mut L {
482493
&mut self.listener

transaction-pool/src/tests/mod.rs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,15 @@ pub type SharedTransaction = Arc<Transaction>;
4848

4949
type TestPool = Pool<Transaction, DummyScoring>;
5050

51+
impl TestPool {
52+
pub fn with_limit(max_count: usize) -> Self {
53+
Self::with_options(Options {
54+
max_count,
55+
..Default::default()
56+
})
57+
}
58+
}
59+
5160
#[test]
5261
fn should_clear_queue() {
5362
// given
@@ -445,9 +454,27 @@ fn should_return_worst_transaction() {
445454

446455
// when
447456
txq.import(b.tx().nonce(0).gas_price(5).new()).unwrap();
457+
txq.import(b.tx().sender(1).nonce(0).gas_price(4).new()).unwrap();
458+
459+
// then
460+
assert_eq!(txq.worst_transaction().unwrap().gas_price, 4.into());
461+
}
462+
463+
#[test]
464+
fn should_return_is_full() {
465+
// given
466+
let b = TransactionBuilder::default();
467+
let mut txq = TestPool::with_limit(2);
468+
assert!(!txq.is_full());
469+
470+
// when
471+
txq.import(b.tx().nonce(0).gas_price(110).new()).unwrap();
472+
assert!(!txq.is_full());
473+
474+
txq.import(b.tx().sender(1).nonce(0).gas_price(100).new()).unwrap();
448475

449476
// then
450-
assert!(txq.worst_transaction().is_some());
477+
assert!(txq.is_full());
451478
}
452479

453480
mod listener {

0 commit comments

Comments
 (0)