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;
202
203 fn on_session_established(&mut self, peer: &mut PeerMetadata<N>);
205
206 fn on_session_closed(&mut self, peer: &mut PeerMetadata<N>);
208}
209
210#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Display)]
212#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
213pub enum TransactionPropagationKind {
214 #[default]
218 All,
219 Trusted,
221 None,
223}
224
225impl<N: NetworkPrimitives> TransactionPropagationPolicy<N> for TransactionPropagationKind {
226 fn can_propagate(&self, peer: &mut PeerMetadata<N>) -> bool {
227 match self {
228 Self::All => true,
229 Self::Trusted => peer.peer_kind.is_trusted(),
230 Self::None => false,
231 }
232 }
233
234 fn on_session_established(&mut self, _peer: &mut PeerMetadata<N>) {}
235
236 fn on_session_closed(&mut self, _peer: &mut PeerMetadata<N>) {}
237}
238
239impl FromStr for TransactionPropagationKind {
240 type Err = String;
241
242 fn from_str(s: &str) -> Result<Self, Self::Err> {
243 match s {
244 "All" | "all" => Ok(Self::All),
245 "Trusted" | "trusted" => Ok(Self::Trusted),
246 "None" | "none" => Ok(Self::None),
247 _ => Err(format!("Invalid transaction propagation policy: {s}")),
248 }
249 }
250}
251
252#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Display)]
254#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
255pub enum TransactionIngressPolicy {
256 #[default]
258 All,
259 Trusted,
261 None,
263}
264
265impl TransactionIngressPolicy {
266 pub const fn allows(&self, peer_kind: PeerKind) -> bool {
268 match self {
269 Self::All => true,
270 Self::Trusted => peer_kind.is_trusted(),
271 Self::None => false,
272 }
273 }
274
275 pub const fn allows_all(&self) -> bool {
277 matches!(self, Self::All)
278 }
279}
280
281impl FromStr for TransactionIngressPolicy {
282 type Err = String;
283
284 fn from_str(s: &str) -> Result<Self, Self::Err> {
285 match s {
286 "All" | "all" => Ok(Self::All),
287 "Trusted" | "trusted" => Ok(Self::Trusted),
288 "None" | "none" => Ok(Self::None),
289 _ => Err(format!("Invalid transaction ingress policy: {s}")),
290 }
291 }
292}
293
294#[derive(Debug, Clone, Copy, PartialEq, Eq)]
298pub enum AnnouncementAcceptance {
299 Accept,
301 Ignore,
303 Reject {
305 penalize_peer: bool,
307 },
308}
309
310pub trait AnnouncementFilteringPolicy<N: NetworkPrimitives>:
313 Send + Sync + Unpin + fmt::Debug + 'static
314{
315 fn decide_on_announcement(&self, ty: u8, hash: &B256, size: usize) -> AnnouncementAcceptance;
317}
318
319#[derive(Debug, Clone, Default)]
322#[non_exhaustive]
323pub struct TypedStrictFilter;
324
325impl<N: NetworkPrimitives> AnnouncementFilteringPolicy<N> for TypedStrictFilter {
326 fn decide_on_announcement(&self, ty: u8, hash: &B256, size: usize) -> AnnouncementAcceptance {
327 if N::PooledTransaction::is_type(ty) {
328 AnnouncementAcceptance::Accept
329 } else {
330 tracing::trace!(target: "net::tx::policy::strict_typed",
331 %ty,
332 %size,
333 %hash,
334 "Invalid or unrecognized transaction type byte. Rejecting entry and recommending peer penalization."
335 );
336 AnnouncementAcceptance::Reject { penalize_peer: true }
337 }
338 }
339}
340
341pub type StrictEthAnnouncementFilter = TypedStrictFilter;
343
344#[derive(Debug, Clone, Default)]
349#[non_exhaustive]
350pub struct TypedRelaxedFilter;
351
352impl<N: NetworkPrimitives> AnnouncementFilteringPolicy<N> for TypedRelaxedFilter {
353 fn decide_on_announcement(&self, ty: u8, hash: &B256, size: usize) -> AnnouncementAcceptance {
354 if N::PooledTransaction::is_type(ty) {
355 AnnouncementAcceptance::Accept
356 } else {
357 tracing::trace!(target: "net::tx::policy::relaxed_typed",
358 %ty,
359 %size,
360 %hash,
361 "Unknown transaction type byte. Ignoring entry."
362 );
363 AnnouncementAcceptance::Ignore
364 }
365 }
366}
367
368pub type RelaxedEthAnnouncementFilter = TypedRelaxedFilter;
371
372#[cfg(test)]
373mod tests {
374 use super::*;
375
376 #[test]
377 fn test_transaction_propagation_mode_from_str() {
378 assert_eq!(
380 TransactionPropagationMode::from_str("sqrt").unwrap(),
381 TransactionPropagationMode::Sqrt
382 );
383 assert_eq!(
384 TransactionPropagationMode::from_str("SQRT").unwrap(),
385 TransactionPropagationMode::Sqrt
386 );
387 assert_eq!(
388 TransactionPropagationMode::from_str("Sqrt").unwrap(),
389 TransactionPropagationMode::Sqrt
390 );
391
392 assert_eq!(
394 TransactionPropagationMode::from_str("all").unwrap(),
395 TransactionPropagationMode::All
396 );
397 assert_eq!(
398 TransactionPropagationMode::from_str("ALL").unwrap(),
399 TransactionPropagationMode::All
400 );
401 assert_eq!(
402 TransactionPropagationMode::from_str("All").unwrap(),
403 TransactionPropagationMode::All
404 );
405
406 assert_eq!(
408 TransactionPropagationMode::from_str("max:10").unwrap(),
409 TransactionPropagationMode::Max(10)
410 );
411 assert_eq!(
412 TransactionPropagationMode::from_str("MAX:42").unwrap(),
413 TransactionPropagationMode::Max(42)
414 );
415 assert_eq!(
416 TransactionPropagationMode::from_str("Max:100").unwrap(),
417 TransactionPropagationMode::Max(100)
418 );
419
420 assert!(TransactionPropagationMode::from_str("invalid").is_err());
422 assert!(TransactionPropagationMode::from_str("max:not_a_number").is_err());
423 assert!(TransactionPropagationMode::from_str("max:").is_err());
424 assert!(TransactionPropagationMode::from_str("max").is_err());
425 assert!(TransactionPropagationMode::from_str("").is_err());
426 }
427}