Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: discard txs by tx_count of sender #4520

Merged
merged 58 commits into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
3034a0a
feat: discard txs by tx_count of sender
chirag-bgh Sep 8, 2023
afa5d59
fix
chirag-bgh Sep 8, 2023
ddc2b85
penalize oldest tx first of the parked pool
chirag-bgh Sep 8, 2023
966f267
penalize oldest tx first of the pending pool
chirag-bgh Sep 8, 2023
63e9e4d
discard pending pool txs wrt tx count
chirag-bgh Sep 8, 2023
9f480bf
wip
chirag-bgh Sep 8, 2023
8318dcc
mirror geth algo for discarding txs in parked pool
chirag-bgh Sep 8, 2023
de4918f
fmt
chirag-bgh Sep 8, 2023
1707544
clippy
chirag-bgh Sep 8, 2023
ec319e9
nit
chirag-bgh Sep 8, 2023
c525e15
add test for truncate
Rjected Oct 25, 2023
aa800c5
test spammers method
Rjected Oct 25, 2023
a4dbbe7
use HashMap for get_spammers instead of n^2 iteration
Rjected Oct 25, 2023
84085f1
convert to hashmap for better tracking
Rjected Oct 25, 2023
eb52fe4
re-add threshold for get_spammers
Rjected Oct 25, 2023
a5ff180
remove txs_by_sender
Rjected Oct 25, 2023
75db7b0
fix hanging loop
Rjected Oct 27, 2023
fec5ee0
fix test
Rjected Oct 27, 2023
8e61b6d
small parked improvements
Rjected Nov 1, 2023
c522784
add test for parked truncate operation
Rjected Nov 2, 2023
7b1b267
improve docs
Rjected Nov 2, 2023
55d4f03
update test comments
Rjected Nov 2, 2023
157a3d3
cargo fmt
Rjected Nov 3, 2023
8e8997a
rework pending eviction to care only about tx limit and size limit
Rjected Nov 16, 2023
bff5e3f
update test for new descendant eviction logic
Rjected Nov 16, 2023
9b2ec78
use EvictionEntry map for truncation
Rjected Nov 16, 2023
fa96b67
fix comment
Rjected Nov 16, 2023
ca6a5de
use by_id BTreeMap
Rjected Nov 21, 2023
669a920
remove by_sender
Rjected Nov 21, 2023
27ee90f
add TODO for test change
Rjected Nov 21, 2023
e666399
remove unnecessary txid method
Rjected Nov 21, 2023
ae10bf7
remove ancestor instead of retaining descendants set
Rjected Nov 21, 2023
f5081c4
rename independent_descendants to highest_nonces
Rjected Nov 21, 2023
615b119
use `next` method for easier tx chains
Rjected Nov 22, 2023
d4313e4
add benchmarks for parked pool truncate method
Rjected Nov 22, 2023
d3d1144
optimize case when no txs need to be removed from parked
Rjected Nov 22, 2023
18398dd
return early for pending pool if not full
Rjected Nov 22, 2023
c585018
use the `SenderId` instead of address
Rjected Nov 23, 2023
0252e2e
cargo fmt
Rjected Nov 23, 2023
3b723cc
rename update_independents method
Rjected Nov 23, 2023
5fd7bb6
use loop { } for pending truncate method
Rjected Nov 23, 2023
045ff6e
add transactions_to_remove for gathering txs to truncate
Rjected Nov 23, 2023
ad0ddcd
make truncate method use ids, update tests accordingly
Rjected Nov 23, 2023
68f0674
use `range` for txs by sender
Rjected Nov 23, 2023
e07d1a9
add pending pool truncate to benchmark
Rjected Nov 23, 2023
5644944
test decreasing nonce for to_remove
Rjected Nov 23, 2023
8ae7b8d
use explicit return in first loop
Rjected Nov 23, 2023
de57cf2
add comment on convenience method
Rjected Nov 23, 2023
f185b11
return TransactionId instead of transaction
Rjected Nov 23, 2023
29769f4
rename locals arg, switch `removed` init order
Rjected Nov 24, 2023
969784a
remove transactions within loop
Rjected Nov 24, 2023
42da81a
rename to limit_pool
Rjected Nov 24, 2023
5020b3f
add detail about iteration
Rjected Nov 24, 2023
c45fafa
improve truncate_pool and limit_pool docs
Rjected Nov 25, 2023
8a66895
improve truncate_pool docs
Rjected Nov 25, 2023
4a1ef9d
remove commented tests
Rjected Nov 25, 2023
3f3798a
rename to `remove_to_limit`
Rjected Nov 27, 2023
c60e831
fix truncate_pool docs
Rjected Nov 27, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions crates/transaction-pool/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ serde = ["dep:serde"]
test-utils = ["rand", "paste", "serde"]
arbitrary = ["proptest", "reth-primitives/arbitrary"]

[[bench]]
name = "truncate"
required-features = ["test-utils", "arbitrary"]
harness = false

[[bench]]
name = "reorder"
required-features = ["test-utils", "arbitrary"]
Expand Down
198 changes: 198 additions & 0 deletions crates/transaction-pool/benches/truncate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
use criterion::{
criterion_group, criterion_main, measurement::WallTime, BenchmarkGroup, Criterion,
};
use proptest::{
prelude::*,
strategy::{Strategy, ValueTree},
test_runner::{RngAlgorithm, TestRng, TestRunner},
};
use reth_primitives::{hex_literal::hex, Address};
use reth_transaction_pool::{
pool::{BasefeeOrd, ParkedPool, PendingPool},
test_utils::{MockOrdering, MockTransaction, MockTransactionFactory},
SubPoolLimit,
};

// constant seed to use for the rng
const SEED: [u8; 32] = hex!("1337133713371337133713371337133713371337133713371337133713371337");

/// Generates a set of `depth` dependent transactions, with the specified sender. Its values are
/// generated using [Arbitrary].
fn create_transactions_for_sender(
mut runner: TestRunner,
sender: Address,
depth: usize,
) -> Vec<MockTransaction> {
// TODO: for blob truncate, this would need a flag for _only_ generating 4844 mock transactions

// assert that depth is always greater than zero, since empty vecs do not really make sense in
// this context
assert!(depth > 0);

// make sure these are all post-eip-1559 transactions
let mut txs = prop::collection::vec(any::<MockTransaction>(), depth)
.new_tree(&mut runner)
.unwrap()
.current();

let mut nonce = 0;
for tx in txs.iter_mut() {
// reject pre-eip1559 tx types, if there is a legacy tx, replace it with an eip1559 tx
if tx.is_legacy() || tx.is_eip2930() {
*tx = MockTransaction::eip1559();

// set fee values using arbitrary
tx.set_priority_fee(any::<u128>().new_tree(&mut runner).unwrap().current());
tx.set_max_fee(any::<u128>().new_tree(&mut runner).unwrap().current());
}

tx.set_sender(sender);
tx.set_nonce(nonce);
nonce += 1;
}

txs
}

/// Generates many transactions, each with a different sender. The number of transactions per
/// sender is generated using [Arbitrary]. The number of senders is specified by `senders`.
///
/// Because this uses [Arbitrary], the number of transactions per sender needs to be bounded. This
/// is done by using the `max_depth` parameter.
///
/// This uses [create_transactions_for_sender] to generate the transactions.
fn generate_many_transactions(senders: usize, max_depth: usize) -> Vec<MockTransaction> {
let config = ProptestConfig::default();
let rng = TestRng::from_seed(RngAlgorithm::ChaCha, &SEED);
let mut runner = TestRunner::new_with_rng(config, rng);

let mut txs = Vec::new();
for idx in 0..senders {
// modulo max_depth so we know it is bounded, plus one so the minimum is always 1
let depth = any::<usize>().new_tree(&mut runner).unwrap().current() % max_depth + 1;

// set sender to an Address determined by the sender index. This should make any necessary
// debugging easier.
let idx_slice = idx.to_be_bytes();

// pad with 12 bytes of zeros before rest
let addr_slice = [0u8; 12].into_iter().chain(idx_slice.into_iter()).collect::<Vec<_>>();

let sender = Address::from_slice(&addr_slice);
txs.extend(create_transactions_for_sender(runner.clone(), sender, depth));
}

txs
}

fn txpool_truncate(c: &mut Criterion) {
let mut group = c.benchmark_group("Transaction Pool Truncate");

// the first few benchmarks (5, 10, 20, 100) should cause the txpool to hit the max tx limit,
// so they are there to make sure we do not regress on best-case performance.
//
// the last few benchmarks (1000, 2000) should hit the max tx limit, at least for large enough
// depth, so these should benchmark closer to real-world performance
for senders in [5, 10, 20, 100, 1000, 2000] {
// the max we'll be benching is 20, because MAX_ACCOUNT_SLOTS so far is 16. So 20 should be
// a reasonable worst-case benchmark
for max_depth in [5, 10, 20] {
println!("Generating transactions for benchmark with {senders} unique senders and a max depth of {max_depth}...");
let txs = generate_many_transactions(senders, max_depth);

// benchmark parked pool
truncate_parked(&mut group, "ParkedPool", txs.clone(), senders, max_depth);

// benchmark pending pool
truncate_pending(&mut group, "PendingPool", txs, senders, max_depth);

// TODO: benchmark blob truncate
}
}

let large_senders = 5000;
let max_depth = 16;

// let's run a benchmark that includes a large number of senders and max_depth of 16 to ensure
// we hit the TXPOOL_SUBPOOL_MAX_TXS_DEFAULT limit, which is currently 10k
println!("Generating transactions for large benchmark with {large_senders} unique senders and a max depth of {max_depth}...");
let txs = generate_many_transactions(large_senders, max_depth);

// benchmark parked
truncate_parked(&mut group, "ParkedPool", txs.clone(), large_senders, max_depth);

// benchmark pending
truncate_pending(&mut group, "PendingPool", txs, large_senders, max_depth);
}

fn truncate_pending(
group: &mut BenchmarkGroup<WallTime>,
description: &str,
seed: Vec<MockTransaction>,
senders: usize,
max_depth: usize,
) {
let setup = || {
let mut txpool = PendingPool::new(MockOrdering::default());
let mut f = MockTransactionFactory::default();

for tx in seed.iter() {
// add transactions with a basefee of zero, so they are not immediately removed
txpool.add_transaction(f.validated_arc(tx.clone()), 0);
}
txpool
};

let group_id = format!(
"txpool | total txs: {} | total senders: {} | max depth: {} | {}",
seed.len(),
senders,
max_depth,
description,
);

// for now we just use the default SubPoolLimit
group.bench_function(group_id, |b| {
b.iter_with_setup(setup, |mut txpool| {
txpool.truncate_pool(SubPoolLimit::default());
std::hint::black_box(());
});
});
}

fn truncate_parked(
group: &mut BenchmarkGroup<WallTime>,
description: &str,
seed: Vec<MockTransaction>,
senders: usize,
max_depth: usize,
) {
let setup = || {
let mut txpool = ParkedPool::<BasefeeOrd<_>>::default();
let mut f = MockTransactionFactory::default();

for tx in seed.iter() {
txpool.add_transaction(f.validated_arc(tx.clone()));
}
txpool
};

let group_id = format!(
"txpool | total txs: {} | total senders: {} | max depth: {} | {}",
seed.len(),
senders,
max_depth,
description,
);

// for now we just use the default SubPoolLimit
group.bench_function(group_id, |b| {
b.iter_with_setup(setup, |mut txpool| {
txpool.truncate_pool(SubPoolLimit::default());
std::hint::black_box(());
});
});
}

criterion_group!(truncate, txpool_truncate);
criterion_main!(truncate);
2 changes: 2 additions & 0 deletions crates/transaction-pool/src/pool/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ use crate::{
};
use alloy_rlp::Encodable;
pub use listener::{AllTransactionsEvents, TransactionEvents};
pub use parked::{BasefeeOrd, ParkedOrd, ParkedPool};
pub use pending::PendingPool;

mod best;
mod blob;
Expand Down
Loading