Skip to content

Commit 35ef56f

Browse files
shekhirinpepyakin
authored andcommitted
feat(net): share sender recovery cache across transaction paths
1 parent e61333f commit 35ef56f

12 files changed

Lines changed: 168 additions & 18 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,7 @@ tracing-appender = "0.2.5"
532532
url = { version = "2.3", default-features = false }
533533
zstd = "0.13"
534534
byteorder = "1"
535-
fixed-cache = { version = "0.1.10", features = ["stats"] }
535+
fixed-cache = { version = "0.1.10", default-features = false, features = ["stats"] }
536536
moka = "0.12"
537537
tar-no-std = { version = "0.4.2", default-features = false }
538538
miniz_oxide = { version = "0.9.0", default-features = false }

crates/ethereum/evm/src/lib.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ use reth_chainspec::{ChainSpec, EthChainSpec, MAINNET};
3030
use reth_ethereum_primitives::{Block, EthPrimitives, TransactionSigned};
3131
use reth_evm::{
3232
eth::NextEvmEnvAttributes, precompiles::PrecompilesMap, ConfigureEvm, EvmEnv, EvmFactory,
33-
JitBackend, NextBlockEnvAttributes, TransactionEnvMut,
33+
JitBackend, NextBlockEnvAttributes, SenderRecoveryCache, TransactionEnvMut,
3434
};
3535
use reth_primitives_traits::{SealedBlock, SealedHeader};
3636
use revm::{context::BlockEnv, primitives::hardfork::SpecId};
@@ -87,6 +87,8 @@ pub struct EthEvmConfig<C = ChainSpec, EvmFactory = EthEvmFactory> {
8787
pub executor_factory: EthBlockExecutorFactory<RethReceiptBuilder, Arc<C>, EvmFactory>,
8888
/// Ethereum block assembler.
8989
pub block_assembler: EthBlockAssembler<C>,
90+
/// Cache of recovered transaction senders.
91+
pub sender_recovery_cache: SenderRecoveryCache,
9092
}
9193

9294
impl EthEvmConfig {
@@ -113,6 +115,7 @@ impl<ChainSpec, EvmFactory> EthEvmConfig<ChainSpec, EvmFactory> {
113115
pub fn new_with_evm_factory(chain_spec: Arc<ChainSpec>, evm_factory: EvmFactory) -> Self {
114116
Self {
115117
block_assembler: EthBlockAssembler::new(chain_spec.clone()),
118+
sender_recovery_cache: SenderRecoveryCache::default(),
116119
executor_factory: EthBlockExecutorFactory::new(
117120
RethReceiptBuilder::default(),
118121
chain_spec,
@@ -125,6 +128,12 @@ impl<ChainSpec, EvmFactory> EthEvmConfig<ChainSpec, EvmFactory> {
125128
pub const fn chain_spec(&self) -> &Arc<ChainSpec> {
126129
self.executor_factory.spec()
127130
}
131+
132+
/// Uses the provided sender recovery cache.
133+
pub fn with_sender_recovery_cache(mut self, cache: SenderRecoveryCache) -> Self {
134+
self.sender_recovery_cache = cache;
135+
self
136+
}
128137
}
129138

130139
impl<ChainSpec, EvmF> ConfigureEvm for EthEvmConfig<ChainSpec, EvmF>
@@ -345,10 +354,11 @@ where
345354
payload: &ExecutionData,
346355
) -> Result<impl ExecutableTxIterator<Self>, Self::Error> {
347356
let txs = payload.payload.transactions().clone();
348-
let convert = |tx: Bytes| {
357+
let sender_recovery_cache = self.sender_recovery_cache.clone();
358+
let convert = move |tx: Bytes| {
349359
let tx =
350360
TxTy::<Self::Primitives>::decode_2718_exact(tx.as_ref()).map_err(AnyError::new)?;
351-
let signer = tx.try_recover().map_err(AnyError::new)?;
361+
let signer = sender_recovery_cache.recover(&tx).map_err(AnyError::new)?;
352362
Ok::<_, AnyError>(tx.with_signer(signer))
353363
};
354364

crates/ethereum/node/src/node.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,7 @@ where
608608
let dump_dir = jit.debug.then(|| ctx.config().datadir().data_dir().join("jit"));
609609

610610
let (evm_config, revmc_metrics) = build_evm_config(ctx.chain_spec(), jit, dump_dir)?;
611+
let evm_config = evm_config.with_sender_recovery_cache(ctx.sender_recovery_cache().clone());
611612

612613
#[cfg(not(feature = "jit"))]
613614
let _ = revmc_metrics;

crates/evm/evm/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ derive_more.workspace = true
3434
futures-util.workspace = true
3535
metrics = { workspace = true, optional = true }
3636
rayon = { workspace = true, optional = true }
37+
fixed-cache.workspace = true
3738

3839
[dev-dependencies]
3940
reth-ethereum-primitives.workspace = true
@@ -57,6 +58,7 @@ std = [
5758
"reth-trie-common/std",
5859
"reth-ethereum-primitives/std",
5960
"alloy-eip7928/std",
61+
"fixed-cache/std",
6062
]
6163
metrics = ["std", "dep:metrics", "dep:reth-metrics"]
6264
test-utils = [

crates/evm/evm/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ pub use aliases::*;
4444
mod engine;
4545
#[cfg(feature = "std")]
4646
pub use engine::{ConfigureEngineEvm, ConvertTx, ExecutableTxIterator, ExecutableTxTuple};
47+
mod sender_recovery;
48+
pub use sender_recovery::SenderRecoveryCache;
4749

4850
#[cfg(feature = "metrics")]
4951
pub mod metrics;
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
use alloc::sync::Arc;
2+
use alloy_primitives::{map::FbBuildHasher, Address, B256};
3+
use reth_primitives_traits::{transaction::signed::RecoveryError, SignedTransaction};
4+
5+
/// Number of entries retained in the default sender recovery cache.
6+
const SENDER_RECOVERY_CACHE_CAPACITY: usize = 1 << 17;
7+
8+
/// Shared cache of recovered transaction senders.
9+
///
10+
/// Sender recovery is performed when a transaction enters the pool and again when the same
11+
/// transaction is received in an execution payload. Sharing this bounded, lock-free cache lets
12+
/// payload prewarming reuse the result produced by transaction-pool ingress.
13+
#[derive(Clone, Debug)]
14+
pub struct SenderRecoveryCache {
15+
cache: Arc<fixed_cache::Cache<B256, Address, FbBuildHasher<32>>>,
16+
}
17+
18+
impl SenderRecoveryCache {
19+
/// Creates a sender recovery cache with the given capacity.
20+
///
21+
/// The capacity must satisfy [`fixed_cache::Cache`] capacity requirements.
22+
pub fn new(capacity: usize) -> Self {
23+
Self { cache: Arc::new(fixed_cache::Cache::new(capacity, FbBuildHasher::<32>::default())) }
24+
}
25+
26+
/// Returns a cached sender for the transaction hash.
27+
#[inline]
28+
pub fn get(&self, tx_hash: &B256) -> Option<Address> {
29+
self.cache.get(tx_hash)
30+
}
31+
32+
/// Returns the cached sender or recovers and caches it on a miss.
33+
///
34+
/// Failed recoveries are not cached.
35+
#[inline]
36+
pub fn recover<T: SignedTransaction>(&self, transaction: &T) -> Result<Address, RecoveryError> {
37+
self.cache.get_or_try_insert_with_ref(
38+
transaction.tx_hash(),
39+
|_| transaction.try_recover(),
40+
|hash| *hash,
41+
)
42+
}
43+
}
44+
45+
impl Default for SenderRecoveryCache {
46+
fn default() -> Self {
47+
Self::new(SENDER_RECOVERY_CACHE_CAPACITY)
48+
}
49+
}
50+
51+
#[cfg(test)]
52+
mod tests {
53+
use super::*;
54+
use alloy_consensus::TxLegacy;
55+
use alloy_primitives::{Signature, U256};
56+
use reth_ethereum_primitives::{Transaction, TransactionSigned};
57+
58+
#[test]
59+
fn recover_populates_cache() {
60+
let transaction = TransactionSigned::new_unhashed(
61+
Transaction::Legacy(TxLegacy::default()),
62+
Signature::test_signature(),
63+
);
64+
let cache = SenderRecoveryCache::new(4);
65+
let shared_cache = cache.clone();
66+
67+
let sender = cache.recover(&transaction).unwrap();
68+
69+
assert_eq!(shared_cache.get(transaction.tx_hash()), Some(sender));
70+
assert_eq!(shared_cache.recover(&transaction).unwrap(), sender);
71+
}
72+
73+
#[test]
74+
fn failed_recovery_is_not_cached() {
75+
let transaction = TransactionSigned::new_unhashed(
76+
Transaction::Legacy(TxLegacy::default()),
77+
Signature::new(U256::ZERO, U256::ZERO, false),
78+
);
79+
let cache = SenderRecoveryCache::new(4);
80+
81+
assert!(cache.recover(&transaction).is_err());
82+
assert_eq!(cache.get(transaction.tx_hash()), None);
83+
}
84+
}

crates/net/network/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ reth-discv4.workspace = true
2424
reth-discv5.workspace = true
2525
reth-dns-discovery.workspace = true
2626
reth-ethereum-forks.workspace = true
27+
reth-evm.workspace = true
2728
reth-eth-wire.workspace = true
2829
reth-eth-wire-types.workspace = true
2930
reth-ecies.workspace = true
@@ -139,4 +140,5 @@ test-utils = [
139140
"dep:reth-evm-ethereum",
140141
"reth-evm-ethereum?/test-utils",
141142
"reth-tasks/test-utils",
143+
"reth-evm/test-utils",
142144
]

crates/net/network/src/builder.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,15 @@ pub struct NetworkBuilder<Tx, Eth, N: NetworkPrimitives = EthNetworkPrimitives>
3434
// === impl NetworkBuilder ===
3535

3636
impl<Tx, Eth, N: NetworkPrimitives> NetworkBuilder<Tx, Eth, N> {
37+
/// Maps the transactions component.
38+
pub fn map_transactions<F, NewTx>(self, f: F) -> NetworkBuilder<NewTx, Eth, N>
39+
where
40+
F: FnOnce(Tx) -> NewTx,
41+
{
42+
let Self { network, transactions, request_handler } = self;
43+
NetworkBuilder { network, transactions: f(transactions), request_handler }
44+
}
45+
3746
/// Consumes the type and returns all fields.
3847
pub fn split(self) -> (NetworkManager<N>, Tx, Eth) {
3948
let Self { network, transactions, request_handler } = self;

crates/net/network/src/transactions/mod.rs

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ use reth_eth_wire::{
5555
PooledTransactions, RequestTxHashes, Transactions, ValidAnnouncementData,
5656
};
5757
use reth_ethereum_primitives::TxType;
58+
use reth_evm::SenderRecoveryCache;
5859
use reth_metrics::common::mpsc::MemoryBoundedReceiver;
5960
use reth_network_api::{
6061
events::{PeerEvent, SessionInfo},
@@ -285,6 +286,8 @@ impl<N: NetworkPrimitives> TransactionsHandle<N> {
285286
pub struct TransactionsManager<Pool, N: NetworkPrimitives = EthNetworkPrimitives> {
286287
/// Access to the transaction pool.
287288
pool: Pool,
289+
/// Cache of recovered transaction senders shared with payload prewarming.
290+
sender_recovery_cache: SenderRecoveryCache,
288291
/// Network access.
289292
network: NetworkHandle<N>,
290293
/// Subscriptions to all network related events.
@@ -400,6 +403,7 @@ impl<Pool: TransactionPool, N: NetworkPrimitives> TransactionsManager<Pool, N> {
400403

401404
Self {
402405
pool,
406+
sender_recovery_cache: SenderRecoveryCache::default(),
403407
network,
404408
network_events,
405409
transaction_fetcher,
@@ -424,6 +428,12 @@ impl<Pool: TransactionPool, N: NetworkPrimitives> TransactionsManager<Pool, N> {
424428
TransactionsHandle { manager_tx: self.command_tx.clone() }
425429
}
426430

431+
/// Uses the provided sender recovery cache.
432+
pub fn with_sender_recovery_cache(mut self, cache: SenderRecoveryCache) -> Self {
433+
self.sender_recovery_cache = cache;
434+
self
435+
}
436+
427437
/// Returns `true` if [`TransactionsManager`] has capacity to request pending hashes. Returns
428438
/// `false` if [`TransactionsManager`] is operating close to full capacity.
429439
fn has_capacity_for_fetching_pending_hashes(&self) -> bool {
@@ -1454,18 +1464,19 @@ where
14541464

14551465
let txs_len = transactions.len();
14561466

1457-
let recover = |tx| match Pool::Transaction::try_recover(tx) {
1458-
Ok(tx) => Some(tx),
1459-
Err(badtx) => {
1460-
trace!(target: "net::tx",
1461-
peer_id=format!("{peer_id:#}"),
1462-
hash=%badtx.tx_hash(),
1463-
client_version=%client_version,
1464-
"failed ecrecovery for transaction"
1465-
);
1466-
None
1467-
}
1468-
};
1467+
let recover =
1468+
|tx| match Pool::Transaction::try_recover_with_cache(tx, &self.sender_recovery_cache) {
1469+
Ok(tx) => Some(tx),
1470+
Err(badtx) => {
1471+
trace!(target: "net::tx",
1472+
peer_id=format!("{peer_id:#}"),
1473+
hash=%badtx.tx_hash(),
1474+
client_version=%client_version,
1475+
"failed ecrecovery for transaction"
1476+
);
1477+
None
1478+
}
1479+
};
14691480

14701481
let new_txs = transactions.into_par_iter().filter_map(recover).collect::<Vec<_>>();
14711482

0 commit comments

Comments
 (0)