reth_network/transactions/
config.rs1use core::fmt;
2use std::{fmt::Debug, str::FromStr};
3
4use super::{
5 PeerMetadata, DEFAULT_MAX_COUNT_TRANSACTIONS_SEEN_BY_PEER,
6 DEFAULT_SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESP_ON_PACK_GET_POOLED_TRANSACTIONS_REQ,
7 SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESPONSE,
8};
9use crate::transactions::constants::{
10 tx_fetcher::{
11 DEFAULT_MAX_CAPACITY_CACHE_PENDING_FETCH, DEFAULT_MAX_COUNT_ANNOUNCED_HASHES_PER_PEER,
12 DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS, DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS_PER_PEER,
13 },
14 tx_manager::{
15 DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS, DEFAULT_TX_MANAGER_CHANNEL_MEMORY_LIMIT_BYTES,
16 },
17};
18use alloy_eips::eip2718::IsTyped2718;
19use alloy_primitives::B256;
20use derive_more::Display;
21use reth_eth_wire::NetworkPrimitives;
22use reth_network_types::peers::kind::PeerKind;
23
24#[derive(Debug, Clone)]
26#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
27pub struct TransactionsManagerConfig {
28 pub transaction_fetcher_config: TransactionFetcherConfig,
30 pub max_transactions_seen_by_peer_history: u32,
32 #[cfg_attr(feature = "serde", serde(default = "default_max_pending_pool_imports"))]
35 pub max_pending_pool_imports: usize,
36 #[cfg_attr(feature = "serde", serde(default))]
38 pub propagation_mode: TransactionPropagationMode,
39 #[cfg_attr(feature = "serde", serde(default))]
41 pub ingress_policy: TransactionIngressPolicy,
42 #[cfg_attr(feature = "serde", serde(default = "default_tx_channel_memory_limit_bytes"))]
47 pub tx_channel_memory_limit_bytes: usize,
48}
49
50#[cfg(feature = "serde")]
51const fn default_max_pending_pool_imports() -> usize {
52 DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS
53}
54
55#[cfg(feature = "serde")]
56const fn default_tx_channel_memory_limit_bytes() -> usize {
57 DEFAULT_TX_MANAGER_CHANNEL_MEMORY_LIMIT_BYTES
58}
59
60impl Default for TransactionsManagerConfig {
61 fn default() -> Self {
62 Self {
63 transaction_fetcher_config: TransactionFetcherConfig::default(),
64 max_transactions_seen_by_peer_history: DEFAULT_MAX_COUNT_TRANSACTIONS_SEEN_BY_PEER,
65 max_pending_pool_imports: DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS,
66 propagation_mode: TransactionPropagationMode::default(),
67 ingress_policy: TransactionIngressPolicy::default(),
68 tx_channel_memory_limit_bytes: DEFAULT_TX_MANAGER_CHANNEL_MEMORY_LIMIT_BYTES,
69 }
70 }
71}
72
73#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
75#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
76pub enum TransactionPropagationMode {
77 #[default]
79 Sqrt,
80 All,
82 Max(usize),
84}
85
86impl TransactionPropagationMode {
87 pub(crate) fn full_peer_count(&self, peer_count: usize) -> usize {
89 match self {
90 Self::Sqrt => (peer_count as f64).sqrt().round() as usize,
91 Self::All => peer_count,
92 Self::Max(max) => peer_count.min(*max),
93 }
94 }
95}
96impl std::fmt::Display for TransactionPropagationMode {
97 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98 match self {
99 Self::Sqrt => write!(f, "sqrt"),
100 Self::All => write!(f, "all"),
101 Self::Max(max) => write!(f, "max:{max}"),
102 }
103 }
104}
105
106impl FromStr for TransactionPropagationMode {
107 type Err = String;
108
109 fn from_str(s: &str) -> Result<Self, Self::Err> {
110 let s = s.to_lowercase();
111 match s.as_str() {
112 "sqrt" => Ok(Self::Sqrt),
113 "all" => Ok(Self::All),
114 s => {
115 if let Some(num) = s.strip_prefix("max:") {
116 num.parse::<usize>()
117 .map(TransactionPropagationMode::Max)
118 .map_err(|_| format!("Invalid number for Max variant: {num}"))
119 } else {
120 Err(format!("Invalid transaction propagation mode: {s}"))
121 }
122 }
123 }
124 }
125}
126
127#[derive(Debug, Clone)]
129#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
130pub struct TransactionFetcherConfig {
131 pub max_inflight_requests: u32,
133 pub max_inflight_requests_per_peer: u8,
136 pub soft_limit_byte_size_pooled_transactions_response: usize,
141 pub soft_limit_byte_size_pooled_transactions_response_on_pack_request: usize,
145 pub max_capacity_cache_txns_pending_fetch: u32,
151 #[cfg_attr(feature = "serde", serde(default = "default_max_announced_hashes_per_peer"))]
154 pub max_announced_hashes_per_peer: u32,
155}
156
157#[cfg(feature = "serde")]
158const fn default_max_announced_hashes_per_peer() -> u32 {
159 DEFAULT_MAX_COUNT_ANNOUNCED_HASHES_PER_PEER
160}
161
162impl TransactionFetcherConfig {
163 pub const fn new(
165 max_inflight_requests: u32,
166 max_inflight_requests_per_peer: u8,
167 soft_limit_byte_size_pooled_transactions_response: usize,
168 soft_limit_byte_size_pooled_transactions_response_on_pack_request: usize,
169 max_capacity_cache_txns_pending_fetch: u32,
170 ) -> Self {
171 Self {
172 max_inflight_requests,
173 max_inflight_requests_per_peer,
174 soft_limit_byte_size_pooled_transactions_response,
175 soft_limit_byte_size_pooled_transactions_response_on_pack_request,
176 max_capacity_cache_txns_pending_fetch,
177 max_announced_hashes_per_peer: DEFAULT_MAX_COUNT_ANNOUNCED_HASHES_PER_PEER,
178 }
179 }
180}
181
182impl Default for TransactionFetcherConfig {
183 fn default() -> Self {
184 Self::new(
185 DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS,
186 DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS_PER_PEER,
187 SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESPONSE,
188 DEFAULT_SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESP_ON_PACK_GET_POOLED_TRANSACTIONS_REQ,
189 DEFAULT_MAX_CAPACITY_CACHE_PENDING_FETCH,
190 )
191 }
192}
193
194pub trait TransactionPropagationPolicy<N: NetworkPrimitives>:
196 Send + Sync + Unpin + fmt::Debug + 'static
197{
198 fn can_propagate(&self, peer: &mut PeerMetadata<N>) -> bool;
204
205 fn on_session_established(&mut self, peer: &mut PeerMetadata<N>);
207
208 fn on_session_closed(&mut self, peer: &mut PeerMetadata<N>);
210}
211
212#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Display)]
214#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
215pub enum TransactionPropagationKind {
216 #[default]
220 All,
221 Trusted,
223 None,
225}
226
227impl<N: NetworkPrimitives> TransactionPropagationPolicy<N> for TransactionPropagationKind {
228 fn can_propagate(&self, peer: &mut PeerMetadata<N>) -> bool {
229 match self {
230 Self::All => true,
231 Self::Trusted => peer.peer_kind.is_trusted(),
232 Self::None => false,
233 }
234 }
235
236 fn on_session_established(&mut self, _peer: &mut PeerMetadata<N>) {}
237
238 fn on_session_closed(&mut self, _peer: &mut PeerMetadata<N>) {}
239}
240
241impl FromStr for TransactionPropagationKind {
242 type Err = String;
243
244 fn from_str(s: &str) -> Result<Self, Self::Err> {
245 match s {
246 "All" | "all" => Ok(Self::All),
247 "Trusted" | "trusted" => Ok(Self::Trusted),
248 "None" | "none" => Ok(Self::None),
249 _ => Err(format!("Invalid transaction propagation policy: {s}")),
250 }
251 }
252}
253
254#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Display)]
256#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
257pub enum TransactionIngressPolicy {
258 #[default]
260 All,
261 Trusted,
263 None,
265}
266
267impl TransactionIngressPolicy {
268 pub const fn allows(&self, peer_kind: PeerKind) -> bool {
270 match self {
271 Self::All => true,
272 Self::Trusted => peer_kind.is_trusted(),
273 Self::None => false,
274 }
275 }
276
277 pub const fn allows_all(&self) -> bool {
279 matches!(self, Self::All)
280 }
281}
282
283impl FromStr for TransactionIngressPolicy {
284 type Err = String;
285
286 fn from_str(s: &str) -> Result<Self, Self::Err> {
287 match s {
288 "All" | "all" => Ok(Self::All),
289 "Trusted" | "trusted" => Ok(Self::Trusted),
290 "None" | "none" => Ok(Self::None),
291 _ => Err(format!("Invalid transaction ingress policy: {s}")),
292 }
293 }
294}
295
296#[derive(Debug, Clone, Copy, PartialEq, Eq)]
300pub enum AnnouncementAcceptance {
301 Accept,
303 Ignore,
305 Reject {
307 penalize_peer: bool,
309 },
310}
311
312pub trait AnnouncementFilteringPolicy<N: NetworkPrimitives>:
315 Send + Sync + Unpin + fmt::Debug + 'static
316{
317 fn decide_on_announcement(&self, ty: u8, hash: &B256, size: usize) -> AnnouncementAcceptance;
319}
320
321#[derive(Debug, Clone, Default)]
324#[non_exhaustive]
325pub struct TypedStrictFilter;
326
327impl<N: NetworkPrimitives> AnnouncementFilteringPolicy<N> for TypedStrictFilter {
328 fn decide_on_announcement(&self, ty: u8, hash: &B256, size: usize) -> AnnouncementAcceptance {
329 if N::PooledTransaction::is_type(ty) {
330 AnnouncementAcceptance::Accept
331 } else {
332 tracing::trace!(target: "net::tx::policy::strict_typed",
333 %ty,
334 %size,
335 %hash,
336 "Invalid or unrecognized transaction type byte. Rejecting entry and recommending peer penalization."
337 );
338 AnnouncementAcceptance::Reject { penalize_peer: true }
339 }
340 }
341}
342
343pub type StrictEthAnnouncementFilter = TypedStrictFilter;
345
346#[derive(Debug, Clone, Default)]
351#[non_exhaustive]
352pub struct TypedRelaxedFilter;
353
354impl<N: NetworkPrimitives> AnnouncementFilteringPolicy<N> for TypedRelaxedFilter {
355 fn decide_on_announcement(&self, ty: u8, hash: &B256, size: usize) -> AnnouncementAcceptance {
356 if N::PooledTransaction::is_type(ty) {
357 AnnouncementAcceptance::Accept
358 } else {
359 tracing::trace!(target: "net::tx::policy::relaxed_typed",
360 %ty,
361 %size,
362 %hash,
363 "Unknown transaction type byte. Ignoring entry."
364 );
365 AnnouncementAcceptance::Ignore
366 }
367 }
368}
369
370pub type RelaxedEthAnnouncementFilter = TypedRelaxedFilter;
373
374#[cfg(test)]
375mod tests {
376 use super::*;
377
378 #[test]
379 fn test_transaction_propagation_mode_from_str() {
380 assert_eq!(
382 TransactionPropagationMode::from_str("sqrt").unwrap(),
383 TransactionPropagationMode::Sqrt
384 );
385 assert_eq!(
386 TransactionPropagationMode::from_str("SQRT").unwrap(),
387 TransactionPropagationMode::Sqrt
388 );
389 assert_eq!(
390 TransactionPropagationMode::from_str("Sqrt").unwrap(),
391 TransactionPropagationMode::Sqrt
392 );
393
394 assert_eq!(
396 TransactionPropagationMode::from_str("all").unwrap(),
397 TransactionPropagationMode::All
398 );
399 assert_eq!(
400 TransactionPropagationMode::from_str("ALL").unwrap(),
401 TransactionPropagationMode::All
402 );
403 assert_eq!(
404 TransactionPropagationMode::from_str("All").unwrap(),
405 TransactionPropagationMode::All
406 );
407
408 assert_eq!(
410 TransactionPropagationMode::from_str("max:10").unwrap(),
411 TransactionPropagationMode::Max(10)
412 );
413 assert_eq!(
414 TransactionPropagationMode::from_str("MAX:42").unwrap(),
415 TransactionPropagationMode::Max(42)
416 );
417 assert_eq!(
418 TransactionPropagationMode::from_str("Max:100").unwrap(),
419 TransactionPropagationMode::Max(100)
420 );
421
422 assert!(TransactionPropagationMode::from_str("invalid").is_err());
424 assert!(TransactionPropagationMode::from_str("max:not_a_number").is_err());
425 assert!(TransactionPropagationMode::from_str("max:").is_err());
426 assert!(TransactionPropagationMode::from_str("max").is_err());
427 assert!(TransactionPropagationMode::from_str("").is_err());
428 }
429}