1use std::{
2 collections::HashMap,
3 sync::Arc,
4 time::{Duration, Instant},
5};
6
7use ethrex_blockchain::Blockchain;
8use ethrex_common::H256;
9use ethrex_common::types::{MempoolTransaction, Transaction};
10use ethrex_crypto::NativeCrypto;
11use ethrex_storage::error::StoreError;
12use rand::{seq::SliceRandom, thread_rng};
13use spawned_concurrency::{
14 actor,
15 error::ActorError,
16 protocol,
17 tasks::{Actor, ActorRef, ActorStart as _, Context, Handler, send_interval},
18};
19use tracing::{debug, error, trace};
20
21use crate::{
22 peer_table::{PeerTable, PeerTableServerProtocol as _},
23 rlpx::{
24 Message,
25 connection::server::PeerConnection,
26 eth::transactions::{NewPooledTransactionHashes, Transactions},
27 p2p::{Capability, SUPPORTED_ETH_CAPABILITIES},
28 },
29};
30
31const NEW_POOLED_TRANSACTION_HASHES_SOFT_LIMIT: usize = 4096;
33
34const PRUNE_WAIT_TIME_SECS: u64 = 600; const PRUNE_INTERVAL_SECS: u64 = 360; pub const BROADCAST_INTERVAL_MS: u64 = 1000; #[protocol]
44pub trait TxBroadcasterProtocol: Send + Sync {
45 fn broadcast_txs(&self) -> Result<(), ActorError>;
46 fn add_txs(&self, tx_hashes: Vec<H256>, peer_id: H256) -> Result<(), ActorError>;
47 fn prune_txs(&self) -> Result<(), ActorError>;
48 fn remove_peer(&self, peer_id: H256) -> Result<(), ActorError>;
49}
50
51#[derive(Debug, Clone, Default)]
52struct PeerMask {
53 bits: Vec<u64>,
54}
55
56impl PeerMask {
57 #[inline]
58 fn ensure(&mut self, idx: u32) {
61 let word = (idx as usize) / 64;
62 if self.bits.len() <= word {
63 self.bits.resize(word + 1, 0);
64 }
65 }
66
67 #[inline]
68 fn is_set(&self, idx: u32) -> bool {
69 let word = (idx as usize) / 64;
70 if word >= self.bits.len() {
71 return false;
72 }
73 let bit = (idx as usize) % 64;
74 (self.bits[word] >> bit) & 1 == 1
75 }
76
77 #[inline]
78 fn set(&mut self, idx: u32) {
79 self.ensure(idx);
80 let word = (idx as usize) / 64;
81 let bit = (idx as usize) % 64;
82 self.bits[word] |= 1u64 << bit;
83 }
84
85 #[inline]
86 fn clear(&mut self, idx: u32) {
89 let word = (idx as usize) / 64;
90 if word < self.bits.len() {
91 let bit = (idx as usize) % 64;
92 self.bits[word] &= !(1u64 << bit);
93 }
94 }
95}
96
97#[derive(Debug, Clone)]
98struct BroadcastRecord {
99 peers: PeerMask,
100 last_sent: Instant,
101}
102
103impl Default for BroadcastRecord {
104 fn default() -> Self {
105 Self {
106 peers: PeerMask::default(),
107 last_sent: Instant::now(),
108 }
109 }
110}
111
112#[derive(Debug, Clone)]
113pub struct TxBroadcaster {
114 peer_table: PeerTable,
115 blockchain: Arc<Blockchain>,
116 known_txs: HashMap<H256, BroadcastRecord>,
118 peer_indexer: HashMap<H256, u32>,
120 next_peer_idx: u32,
122 free_indices: Vec<u32>,
125 tx_broadcasting_time_interval: u64,
126}
127
128pub async fn send_tx_hashes(
129 txs: Vec<MempoolTransaction>,
130 capabilities: Vec<Capability>,
131 connection: &mut PeerConnection,
132 peer_id: H256,
133 blockchain: &Arc<Blockchain>,
134) -> Result<(), TxBroadcasterError> {
135 if SUPPORTED_ETH_CAPABILITIES
136 .iter()
137 .any(|cap| capabilities.contains(cap))
138 {
139 for tx_chunk in txs.chunks(NEW_POOLED_TRANSACTION_HASHES_SOFT_LIMIT) {
140 let tx_count = tx_chunk.len();
141 let mut txs_to_send = Vec::with_capacity(tx_count);
142 for tx in tx_chunk {
143 txs_to_send.push((**tx).clone());
144 }
145 let hashes_message = Message::NewPooledTransactionHashes(
146 NewPooledTransactionHashes::new(txs_to_send, blockchain)?,
147 );
148 connection.outgoing_message(hashes_message.clone()).await.unwrap_or_else(|err| {
149 debug!(peer_id = %format!("{:#x}", peer_id), err = ?err, "Failed to send transaction hashes");
150 });
151 }
152 }
153 Ok(())
154}
155
156#[actor(protocol = TxBroadcasterProtocol)]
157impl TxBroadcaster {
158 pub fn spawn(
159 kademlia: PeerTable,
160 blockchain: Arc<Blockchain>,
161 tx_broadcasting_time_interval: u64,
162 ) -> Result<ActorRef<TxBroadcaster>, TxBroadcasterError> {
163 debug!("Starting transaction broadcaster");
164
165 let state = TxBroadcaster {
166 peer_table: kademlia,
167 blockchain,
168 known_txs: HashMap::new(),
169 peer_indexer: HashMap::new(),
170 next_peer_idx: 0,
171 free_indices: Vec::new(),
172 tx_broadcasting_time_interval,
173 };
174
175 Ok(state.start())
176 }
177
178 #[started]
179 async fn started(&mut self, ctx: &Context<Self>) {
180 send_interval(
181 Duration::from_millis(self.tx_broadcasting_time_interval),
182 ctx.clone(),
183 tx_broadcaster_protocol::BroadcastTxs,
184 );
185
186 send_interval(
187 Duration::from_secs(PRUNE_INTERVAL_SECS),
188 ctx.clone(),
189 tx_broadcaster_protocol::PruneTxs,
190 );
191 }
192
193 #[send_handler]
194 async fn handle_broadcast_txs(
195 &mut self,
196 _msg: tx_broadcaster_protocol::BroadcastTxs,
197 _ctx: &Context<Self>,
198 ) {
199 trace!(received = "BroadcastTxs");
200
201 let _ = self.do_broadcast_txs().await.inspect_err(|err| {
202 error!(err = ?err, "Failed to broadcast transactions");
203 });
204 }
205
206 #[send_handler]
207 async fn handle_add_txs(&mut self, msg: tx_broadcaster_protocol::AddTxs, _ctx: &Context<Self>) {
208 debug!(received = "AddTxs", tx_count = msg.tx_hashes.len());
209 self.do_add_txs(msg.tx_hashes, msg.peer_id);
210 }
211
212 #[send_handler]
213 async fn handle_prune_txs(
214 &mut self,
215 _msg: tx_broadcaster_protocol::PruneTxs,
216 _ctx: &Context<Self>,
217 ) {
218 debug!(received = "PruneTxs");
219 let now = Instant::now();
220 let before = self.known_txs.len();
221 let prune_window = Duration::from_secs(PRUNE_WAIT_TIME_SECS);
222
223 self.known_txs
224 .retain(|_, record| now.duration_since(record.last_sent) < prune_window);
225 debug!(
226 before = before,
227 after = self.known_txs.len(),
228 "Pruned old broadcasted transactions"
229 );
230
231 let _ = self.blockchain.mempool.prune_alternates(prune_window);
233 }
234
235 #[send_handler]
236 async fn handle_remove_peer(
237 &mut self,
238 msg: tx_broadcaster_protocol::RemovePeer,
239 _ctx: &Context<Self>,
240 ) {
241 self.do_remove_peer(msg.peer_id);
242 }
243
244 fn do_remove_peer(&mut self, peer_id: H256) {
248 if let Some(idx) = self.peer_indexer.remove(&peer_id) {
249 for record in self.known_txs.values_mut() {
250 record.peers.clear(idx);
251 }
252 self.free_indices.push(idx);
253 }
254 }
255
256 #[inline]
258 fn peer_index(&mut self, peer_id: H256) -> u32 {
259 if let Some(&idx) = self.peer_indexer.get(&peer_id) {
260 idx
261 } else {
262 let idx = self.free_indices.pop().unwrap_or_else(|| {
267 let idx = self.next_peer_idx;
268 self.next_peer_idx += 1;
270 idx
271 });
272 self.peer_indexer.insert(peer_id, idx);
273 idx
274 }
275 }
276
277 fn do_add_txs(&mut self, txs: Vec<H256>, peer_id: H256) {
278 debug!(total = self.known_txs.len(), adding = txs.len(), peer_id = %format!("{:#x}", peer_id), "Adding transactions to known list");
279
280 if txs.is_empty() {
281 return;
282 }
283
284 let now = Instant::now();
285 let peer_idx = self.peer_index(peer_id);
286 for tx in txs {
287 let record = self.known_txs.entry(tx).or_default();
288 record.peers.set(peer_idx);
289 record.last_sent = now;
290 }
291 }
292
293 async fn do_broadcast_txs(&mut self) -> Result<(), TxBroadcasterError> {
294 let txs_to_broadcast = self
295 .blockchain
296 .mempool
297 .get_txs_for_broadcast()
298 .map_err(|_| TxBroadcasterError::Broadcast)?;
299 if txs_to_broadcast.is_empty() {
300 return Ok(());
301 }
302 let peers = self.peer_table.get_peers_with_capabilities().await?;
303 let peer_sqrt = (peers.len() as f64).sqrt();
304
305 let full_txs = txs_to_broadcast
306 .iter()
307 .map(|tx| tx.transaction().clone())
308 .filter(|tx| {
309 !matches!(tx, Transaction::EIP4844Transaction { .. }) && !tx.is_privileged()
310 })
311 .collect::<Vec<Transaction>>();
312
313 let blob_txs = txs_to_broadcast
314 .iter()
315 .filter(|tx| matches!(tx.transaction(), Transaction::EIP4844Transaction { .. }))
316 .cloned()
317 .collect::<Vec<MempoolTransaction>>();
318
319 let mut shuffled_peers = peers.clone();
320 shuffled_peers.shuffle(&mut thread_rng());
321
322 let (peers_to_send_full_txs, peers_to_send_hashes) =
323 shuffled_peers.split_at(peer_sqrt.ceil() as usize);
324
325 for (peer_id, mut connection, capabilities) in peers_to_send_full_txs.iter().cloned() {
326 let peer_idx = self.peer_index(peer_id);
327 let txs_to_send = full_txs
328 .iter()
329 .filter(|tx| {
330 let hash = tx.hash(&NativeCrypto);
331 !self
332 .known_txs
333 .get(&hash)
334 .is_some_and(|record| record.peers.is_set(peer_idx))
335 })
336 .cloned()
337 .collect::<Vec<Transaction>>();
338 self.do_add_txs(
339 txs_to_send
340 .iter()
341 .map(|tx| tx.hash(&NativeCrypto))
342 .collect(),
343 peer_id,
344 );
345 let txs_message = Message::Transactions(Transactions {
347 transactions: txs_to_send,
348 });
349 connection.outgoing_message(txs_message).await.unwrap_or_else(|err| {
350 debug!(peer_id = %format!("{:#x}", peer_id), err = ?err, "Failed to send transactions");
351 });
352 self.send_tx_hashes_internal(blob_txs.clone(), capabilities, &mut connection, peer_id)
353 .await?;
354 }
355 for (peer_id, mut connection, capabilities) in peers_to_send_hashes.iter().cloned() {
356 self.send_tx_hashes_internal(
358 txs_to_broadcast.clone(),
359 capabilities,
360 &mut connection,
361 peer_id,
362 )
363 .await?;
364 }
365 let broadcasted_hashes: Vec<H256> = txs_to_broadcast
366 .iter()
367 .map(|tx| tx.hash(&NativeCrypto))
368 .collect();
369 self.blockchain
370 .mempool
371 .remove_broadcasted_txs(&broadcasted_hashes)?;
372 Ok(())
373 }
374
375 async fn send_tx_hashes_internal(
376 &mut self,
377 txs: Vec<MempoolTransaction>,
378 capabilities: Vec<Capability>,
379 connection: &mut PeerConnection,
380 peer_id: H256,
381 ) -> Result<(), TxBroadcasterError> {
382 let peer_idx = self.peer_index(peer_id);
383 let txs_to_send = txs
384 .iter()
385 .filter(|tx| {
386 let hash = tx.hash(&NativeCrypto);
387 !self
388 .known_txs
389 .get(&hash)
390 .is_some_and(|record| record.peers.is_set(peer_idx))
391 && !tx.is_privileged()
392 })
393 .cloned()
394 .collect::<Vec<MempoolTransaction>>();
395 self.do_add_txs(
396 txs_to_send
397 .iter()
398 .map(|tx| tx.hash(&NativeCrypto))
399 .collect(),
400 peer_id,
401 );
402 send_tx_hashes(
403 txs_to_send,
404 capabilities,
405 connection,
406 peer_id,
407 &self.blockchain,
408 )
409 .await
410 }
411}
412
413#[derive(Debug, thiserror::Error)]
414pub enum TxBroadcasterError {
415 #[error("Failed to broadcast transactions")]
416 Broadcast,
417 #[error(transparent)]
418 StoreError(#[from] StoreError),
419 #[error(transparent)]
420 PeerTableError(#[from] ActorError),
421}