diff --git a/crates/net/network/src/peers/manager.rs b/crates/net/network/src/peers/manager.rs index e3ea70d7619e..7c21bdb783ad 100644 --- a/crates/net/network/src/peers/manager.rs +++ b/crates/net/network/src/peers/manager.rs @@ -916,6 +916,11 @@ impl Peer { Self { kind: PeerKind::Trusted, ..Self::new(addr) } } + /// Returns the reputation of the peer + pub fn reputation(&self) -> i32 { + self.reputation + } + fn with_state(addr: SocketAddr, state: PeerConnectionState) -> Self { Self { addr, diff --git a/crates/net/network/src/test_utils/testnet.rs b/crates/net/network/src/test_utils/testnet.rs index 698d6d2ec53b..3d9b2363277c 100644 --- a/crates/net/network/src/test_utils/testnet.rs +++ b/crates/net/network/src/test_utils/testnet.rs @@ -4,6 +4,7 @@ use crate::{ builder::ETH_REQUEST_CHANNEL_CAPACITY, error::NetworkError, eth_requests::EthRequestHandler, + peers::PeersHandle, protocol::IntoRlpxSubProtocol, transactions::{TransactionsHandle, TransactionsManager}, NetworkConfig, NetworkConfigBuilder, NetworkEvent, NetworkEvents, NetworkHandle, @@ -475,6 +476,10 @@ impl PeerHandle { self.network.peer_id() } + pub fn peer_handle(&self) -> &PeersHandle { + self.network.peers_handle() + } + pub fn local_addr(&self) -> SocketAddr { self.network.local_addr() } diff --git a/crates/net/network/tests/it/txgossip.rs b/crates/net/network/tests/it/txgossip.rs index 00304305ef0e..b395daf1ec53 100644 --- a/crates/net/network/tests/it/txgossip.rs +++ b/crates/net/network/tests/it/txgossip.rs @@ -2,9 +2,10 @@ use rand::thread_rng; use reth_network::test_utils::Testnet; -use reth_primitives::U256; +use reth_primitives::{TransactionSigned, U256}; use reth_provider::test_utils::{ExtendedAccount, MockEthProvider}; use reth_transaction_pool::{test_utils::TransactionGenerator, PoolTransaction, TransactionPool}; +use std::{sync::Arc, time::Duration}; #[tokio::test(flavor = "multi_thread")] async fn test_tx_gossip() { reth_tracing::init_test_tracing(); @@ -42,3 +43,85 @@ async fn test_tx_gossip() { let received = peer1_tx_listener.recv().await.unwrap(); assert_eq!(received, hash); } + +#[tokio::test(flavor = "multi_thread")] +async fn test_4844_tx_gossip_penalization() { + reth_tracing::init_test_tracing(); + let provider = MockEthProvider::default(); + let net = Testnet::create_with(2, provider.clone()).await; + + // install request handlers + let net = net.with_eth_pool(); + + let handle = net.spawn(); + + let peer0 = &handle.peers()[0]; + let peer1 = &handle.peers()[1]; + + // connect all the peers + handle.connect_peers().await; + + let mut peer1_tx_listener = peer1.pool().unwrap().pending_transactions_listener(); + + let mut gen = TransactionGenerator::new(thread_rng()); + + // peer 0 will be penalised for sending txs[0] over gossip + let txs = vec![gen.gen_eip4844_pooled(), gen.gen_eip1559_pooled()]; + + txs.iter().for_each(|tx| { + let sender = tx.sender(); + provider.add_account(sender, ExtendedAccount::new(0, U256::from(100_000_000))); + }); + + let signed_txs: Vec> = + txs.iter().map(|tx| Arc::new(tx.transaction().clone().into_signed())).collect(); + + let network_handle = peer0.network(); + + let peer0_reputation_before = + peer1.peer_handle().peer_by_id(peer0.peer_id().clone()).await.unwrap().reputation(); + + // sends txs directly to peer1 + network_handle.send_transactions(peer1.peer_id().clone(), signed_txs); + + let received = peer1_tx_listener.recv().await.unwrap(); + + let peer0_reputation_after = + peer1.peer_handle().peer_by_id(peer0.peer_id().clone()).await.unwrap().reputation(); + assert_ne!(peer0_reputation_before, peer0_reputation_after); + assert_eq!(received, txs[1].transaction().hash); + + // this will return an [`Empty`] error because blob txs are disallowed to be broadcasted + assert!(peer1_tx_listener.try_recv().is_err()); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_sending_invalid_transactions() { + reth_tracing::init_test_tracing(); + let provider = MockEthProvider::default(); + let net = Testnet::create_with(2, provider.clone()).await; + // install request handlers + let net = net.with_eth_pool(); + + let handle = net.spawn(); + + let peer0 = &handle.peers()[0]; + let peer1 = &handle.peers()[1]; + + // connect all the peers + handle.connect_peers().await; + + let mut peer1_tx_listener = peer1.pool().unwrap().pending_transactions_listener(); + + let invalid_txs: Vec> = vec![Arc::new(TransactionSigned::default())]; + + let network_handle = peer0.network(); + + // sends txs directly to peer1 + network_handle.send_transactions(peer1.peer_id().clone(), invalid_txs); + + tokio::time::sleep(Duration::from_secs(1)).await; + + // this will return an [`Empty`] error because bad txs are disallowed to be broadcasted + assert!(peer1_tx_listener.try_recv().is_err()); +} diff --git a/crates/transaction-pool/src/test_utils/gen.rs b/crates/transaction-pool/src/test_utils/gen.rs index 63958035f9df..020cfd06f96d 100644 --- a/crates/transaction-pool/src/test_utils/gen.rs +++ b/crates/transaction-pool/src/test_utils/gen.rs @@ -2,8 +2,8 @@ use crate::EthPooledTransaction; use rand::Rng; use reth_primitives::{ constants::MIN_PROTOCOL_BASE_FEE, sign_message, AccessList, Address, Bytes, - FromRecoveredTransaction, Transaction, TransactionKind, TransactionSigned, TxEip1559, TxLegacy, - TxValue, B256, MAINNET, + FromRecoveredTransaction, Transaction, TransactionKind, TransactionSigned, TxEip1559, + TxEip4844, TxLegacy, TxValue, B256, MAINNET, }; /// A generator for transactions for testing purposes. @@ -91,12 +91,23 @@ impl TransactionGenerator { self.transaction().into_eip1559() } + /// Creates a new transaction with a random signer + pub fn gen_eip4844(&mut self) -> TransactionSigned { + self.transaction().into_eip4844() + } + /// Generates and returns a pooled EIP-1559 transaction with a random signer. pub fn gen_eip1559_pooled(&mut self) -> EthPooledTransaction { EthPooledTransaction::from_recovered_transaction( self.gen_eip1559().into_ecrecovered().unwrap(), ) } + /// Generates and returns a pooled EIP-4844 transaction with a random signer. + pub fn gen_eip4844_pooled(&mut self) -> EthPooledTransaction { + EthPooledTransaction::from_recovered_transaction( + self.gen_eip4844().into_ecrecovered().unwrap(), + ) + } } /// A Builder type to configure and create a transaction. @@ -162,6 +173,26 @@ impl TransactionBuilder { self.signer, ) } + /// Converts the transaction builder into a transaction format using EIP-4844. + pub fn into_eip4844(self) -> TransactionSigned { + TransactionBuilder::signed( + TxEip4844 { + chain_id: self.chain_id, + nonce: self.nonce, + gas_limit: self.gas_limit, + max_fee_per_gas: self.max_fee_per_gas, + max_priority_fee_per_gas: self.max_priority_fee_per_gas, + to: self.to, + value: self.value, + access_list: self.access_list, + input: self.input, + blob_versioned_hashes: Default::default(), + max_fee_per_blob_gas: Default::default(), + } + .into(), + self.signer, + ) + } /// Signs the provided transaction using the specified signer and returns a signed transaction. fn signed(transaction: Transaction, signer: B256) -> TransactionSigned {