Don't forward packets received from TPU forwards port (#22078)

* Don't forward packets received from TPU forwards port

* Add banking stage test
This commit is contained in:
Justin Starry 2021-12-29 12:34:31 -06:00 committed by GitHub
parent d20a3774db
commit b1d9a2e60e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 209 additions and 53 deletions

View File

@ -392,24 +392,35 @@ impl BankingStage {
data_budget: &DataBudget, data_budget: &DataBudget,
) -> std::io::Result<()> { ) -> std::io::Result<()> {
let packets = Self::filter_valid_packets_for_forwarding(buffered_packet_batches.iter()); let packets = Self::filter_valid_packets_for_forwarding(buffered_packet_batches.iter());
inc_new_counter_info!("banking_stage-forwarded_packets", packets.len());
const INTERVAL_MS: u64 = 100; const INTERVAL_MS: u64 = 100;
const MAX_BYTES_PER_SECOND: usize = 10_000 * 1200; const MAX_BYTES_PER_SECOND: usize = 10_000 * 1200;
const MAX_BYTES_PER_INTERVAL: usize = MAX_BYTES_PER_SECOND * INTERVAL_MS as usize / 1000; const MAX_BYTES_PER_INTERVAL: usize = MAX_BYTES_PER_SECOND * INTERVAL_MS as usize / 1000;
const MAX_BYTES_BUDGET: usize = MAX_BYTES_PER_INTERVAL * 5; const MAX_BYTES_BUDGET: usize = MAX_BYTES_PER_INTERVAL * 5;
data_budget.update(INTERVAL_MS, |bytes| { data_budget.update(INTERVAL_MS, |bytes| {
std::cmp::min(bytes + MAX_BYTES_PER_INTERVAL, MAX_BYTES_BUDGET) std::cmp::min(
bytes.saturating_add(MAX_BYTES_PER_INTERVAL),
MAX_BYTES_BUDGET,
)
}); });
let mut packet_vec = Vec::with_capacity(packets.len()); let packet_vec: Vec<_> = packets
for p in packets { .iter()
if data_budget.take(p.meta.size) { .filter_map(|p| {
packet_vec.push((&p.data[..p.meta.size], tpu_forwards)); if !p.meta.forwarded && data_budget.take(p.meta.size) {
Some((&p.data[..p.meta.size], tpu_forwards))
} else {
None
}
})
.collect();
if !packet_vec.is_empty() {
inc_new_counter_info!("banking_stage-forwarded_packets", packet_vec.len());
if let Err(SendPktsError::IoError(ioerr, _num_failed)) = batch_send(socket, &packet_vec)
{
return Err(ioerr);
} }
} }
if let Err(SendPktsError::IoError(ioerr, _num_failed)) = batch_send(socket, &packet_vec) {
return Err(ioerr);
}
Ok(()) Ok(())
} }
@ -1502,7 +1513,7 @@ mod tests {
system_transaction, system_transaction,
transaction::{Transaction, TransactionError}, transaction::{Transaction, TransactionError},
}, },
solana_streamer::socket::SocketAddrSpace, solana_streamer::{recvmmsg::recv_mmsg, socket::SocketAddrSpace},
solana_transaction_status::TransactionWithStatusMeta, solana_transaction_status::TransactionWithStatusMeta,
solana_vote_program::vote_transaction, solana_vote_program::vote_transaction,
std::{ std::{
@ -2763,16 +2774,15 @@ mod tests {
fn test_forwarder_budget() { fn test_forwarder_budget() {
solana_logger::setup(); solana_logger::setup();
// Create `PacketBatch` with 1 unprocessed packet // Create `PacketBatch` with 1 unprocessed packet
let single_packet_batch = PacketBatch::new(vec![Packet::default()]); let packet = Packet::from_data(None, &[0]).unwrap();
let mut unprocessed_packets: UnprocessedPacketBatches = let single_packet_batch = PacketBatch::new(vec![packet]);
vec![(single_packet_batch, vec![0], false)]
.into_iter()
.collect();
let cluster_info = new_test_cluster_info(Node::new_localhost().info);
let genesis_config_info = create_slow_genesis_config(10_000); let genesis_config_info = create_slow_genesis_config(10_000);
let GenesisConfigInfo { genesis_config, .. } = &genesis_config_info; let GenesisConfigInfo {
genesis_config,
validator_pubkey,
..
} = &genesis_config_info;
let bank = Arc::new(Bank::new_no_wallclock_throttle_for_tests(genesis_config)); let bank = Arc::new(Bank::new_no_wallclock_throttle_for_tests(genesis_config));
let ledger_path = get_tmp_ledger_path!(); let ledger_path = get_tmp_ledger_path!();
@ -2791,17 +2801,155 @@ mod tests {
let (exit, poh_recorder, poh_service, _entry_receiver) = let (exit, poh_recorder, poh_service, _entry_receiver) =
create_test_recorder(&bank, &blockstore, Some(poh_config)); create_test_recorder(&bank, &blockstore, Some(poh_config));
let socket = UdpSocket::bind("0.0.0.0:0").unwrap(); let local_node = Node::new_localhost_with_pubkey(validator_pubkey);
let data_budget = DataBudget::default(); let cluster_info = new_test_cluster_info(local_node.info);
BankingStage::handle_forwarding( let send_socket = UdpSocket::bind("0.0.0.0:0").unwrap();
&ForwardOption::ForwardTransaction, let recv_socket = &local_node.sockets.tpu_forwards[0];
&cluster_info,
&mut unprocessed_packets, let test_cases = vec![
&poh_recorder, ("budget-restricted", DataBudget::restricted(), 0),
&socket, ("budget-available", DataBudget::default(), 1),
false, ];
&data_budget,
for (name, data_budget, expected_num_forwarded) in test_cases {
let mut unprocessed_packet_batches: UnprocessedPacketBatches =
vec![(single_packet_batch.clone(), vec![0], false)]
.into_iter()
.collect();
BankingStage::handle_forwarding(
&ForwardOption::ForwardTransaction,
&cluster_info,
&mut unprocessed_packet_batches,
&poh_recorder,
&send_socket,
true,
&data_budget,
);
recv_socket
.set_nonblocking(expected_num_forwarded == 0)
.unwrap();
let mut packets = vec![Packet::default(); 2];
let (_, num_received) =
recv_mmsg(recv_socket, &mut packets[..]).unwrap_or_default();
assert_eq!(num_received, expected_num_forwarded, "{}", name);
}
exit.store(true, Ordering::Relaxed);
poh_service.join().unwrap();
}
Blockstore::destroy(&ledger_path).unwrap();
}
#[test]
fn test_handle_forwarding() {
solana_logger::setup();
const FWD_PACKET: u8 = 1;
let forwarded_packet = {
let mut packet = Packet::from_data(None, &[FWD_PACKET]).unwrap();
packet.meta.forwarded = true;
packet
};
const NORMAL_PACKET: u8 = 2;
let normal_packet = Packet::from_data(None, &[NORMAL_PACKET]).unwrap();
let packet_batch = PacketBatch::new(vec![forwarded_packet, normal_packet]);
let mut unprocessed_packet_batches: UnprocessedPacketBatches =
vec![(packet_batch, vec![0, 1], false)]
.into_iter()
.collect();
let genesis_config_info = create_slow_genesis_config(10_000);
let GenesisConfigInfo {
genesis_config,
validator_pubkey,
..
} = &genesis_config_info;
let bank = Arc::new(Bank::new_no_wallclock_throttle_for_tests(genesis_config));
let ledger_path = get_tmp_ledger_path!();
{
let blockstore = Arc::new(
Blockstore::open(&ledger_path)
.expect("Expected to be able to open database ledger"),
); );
let poh_config = PohConfig {
// limit tick count to avoid clearing working_bank at
// PohRecord then PohRecorderError(MaxHeightReached) at BankingStage
target_tick_count: Some(bank.max_tick_height() - 1),
..PohConfig::default()
};
let (exit, poh_recorder, poh_service, _entry_receiver) =
create_test_recorder(&bank, &blockstore, Some(poh_config));
let local_node = Node::new_localhost_with_pubkey(validator_pubkey);
let cluster_info = new_test_cluster_info(local_node.info);
let send_socket = UdpSocket::bind("0.0.0.0:0").unwrap();
let recv_socket = &local_node.sockets.tpu_forwards[0];
let test_cases = vec![
("not-forward", ForwardOption::NotForward, true, vec![], 2),
(
"fwd-normal",
ForwardOption::ForwardTransaction,
true,
vec![NORMAL_PACKET],
2,
),
(
"fwd-no-op",
ForwardOption::ForwardTransaction,
true,
vec![],
2,
),
(
"fwd-no-hold",
ForwardOption::ForwardTransaction,
false,
vec![],
0,
),
];
for (name, forward_option, hold, expected_ids, expected_num_unprocessed) in test_cases {
BankingStage::handle_forwarding(
&forward_option,
&cluster_info,
&mut unprocessed_packet_batches,
&poh_recorder,
&send_socket,
hold,
&DataBudget::default(),
);
recv_socket
.set_nonblocking(expected_ids.is_empty())
.unwrap();
let mut packets = vec![Packet::default(); 2];
let (_, num_received) =
recv_mmsg(recv_socket, &mut packets[..]).unwrap_or_default();
assert_eq!(num_received, expected_ids.len(), "{}", name);
for (i, expected_id) in expected_ids.iter().enumerate() {
assert_eq!(packets[i].meta.size, 1);
assert_eq!(packets[i].data[0], *expected_id, "{}", name);
}
let num_unprocessed_packets: usize = unprocessed_packet_batches
.iter()
.map(|(b, ..)| b.packets.len())
.sum();
assert_eq!(
num_unprocessed_packets, expected_num_unprocessed,
"{}",
name
);
}
exit.store(true, Ordering::Relaxed); exit.store(true, Ordering::Relaxed);
poh_service.join().unwrap(); poh_service.join().unwrap();
} }

View File

@ -503,11 +503,7 @@ mod tests {
let validator_vote_keypairs = ValidatorVoteKeypairs::new_rand(); let validator_vote_keypairs = ValidatorVoteKeypairs::new_rand();
let validator_keypairs = vec![&validator_vote_keypairs]; let validator_keypairs = vec![&validator_vote_keypairs];
let GenesisConfigInfo { let GenesisConfigInfo { genesis_config, .. } = create_genesis_config_with_vote_accounts(
genesis_config,
mint_keypair: _,
voting_keypair: _,
} = create_genesis_config_with_vote_accounts(
1_000_000_000, 1_000_000_000,
&validator_keypairs, &validator_keypairs,
vec![100; 1], vec![100; 1],

View File

@ -8,7 +8,7 @@ use {
solana_metrics::{inc_new_counter_debug, inc_new_counter_info}, solana_metrics::{inc_new_counter_debug, inc_new_counter_info},
solana_perf::{packet::PacketBatchRecycler, recycler::Recycler}, solana_perf::{packet::PacketBatchRecycler, recycler::Recycler},
solana_poh::poh_recorder::PohRecorder, solana_poh::poh_recorder::PohRecorder,
solana_sdk::clock::DEFAULT_TICKS_PER_SLOT, solana_sdk::{clock::DEFAULT_TICKS_PER_SLOT, packet::Packet},
solana_streamer::streamer::{self, PacketBatchReceiver, PacketBatchSender}, solana_streamer::streamer::{self, PacketBatchReceiver, PacketBatchSender},
std::{ std::{
net::UdpSocket, net::UdpSocket,
@ -83,10 +83,16 @@ impl FetchStage {
sendr: &PacketBatchSender, sendr: &PacketBatchSender,
poh_recorder: &Arc<Mutex<PohRecorder>>, poh_recorder: &Arc<Mutex<PohRecorder>>,
) -> Result<()> { ) -> Result<()> {
let packet_batch = recvr.recv()?; let mark_forwarded = |packet: &mut Packet| {
packet.meta.forwarded = true;
};
let mut packet_batch = recvr.recv()?;
let mut num_packets = packet_batch.packets.len(); let mut num_packets = packet_batch.packets.len();
packet_batch.packets.iter_mut().for_each(mark_forwarded);
let mut packet_batches = vec![packet_batch]; let mut packet_batches = vec![packet_batch];
while let Ok(packet_batch) = recvr.try_recv() { while let Ok(mut packet_batch) = recvr.try_recv() {
packet_batch.packets.iter_mut().for_each(mark_forwarded);
num_packets += packet_batch.packets.len(); num_packets += packet_batch.packets.len();
packet_batches.push(packet_batch); packet_batches.push(packet_batch);
// Read at most 1K transactions in a loop // Read at most 1K transactions in a loop
@ -115,7 +121,7 @@ impl FetchStage {
} }
fn new_multi_socket( fn new_multi_socket(
sockets: Vec<Arc<UdpSocket>>, tpu_sockets: Vec<Arc<UdpSocket>>,
tpu_forwards_sockets: Vec<Arc<UdpSocket>>, tpu_forwards_sockets: Vec<Arc<UdpSocket>>,
tpu_vote_sockets: Vec<Arc<UdpSocket>>, tpu_vote_sockets: Vec<Arc<UdpSocket>>,
exit: &Arc<AtomicBool>, exit: &Arc<AtomicBool>,
@ -126,7 +132,7 @@ impl FetchStage {
) -> Self { ) -> Self {
let recycler: PacketBatchRecycler = Recycler::warmed(1000, 1024); let recycler: PacketBatchRecycler = Recycler::warmed(1000, 1024);
let tpu_threads = sockets.into_iter().map(|socket| { let tpu_threads = tpu_sockets.into_iter().map(|socket| {
streamer::receiver( streamer::receiver(
socket, socket,
exit, exit,

View File

@ -192,7 +192,7 @@ impl ShredFetchStage {
recycler.clone(), recycler.clone(),
bank_forks.clone(), bank_forks.clone(),
"shred_fetch_tvu_forwards", "shred_fetch_tvu_forwards",
|p| p.meta.forward = true, |p| p.meta.forwarded = true,
); );
let (repair_receiver, repair_handler) = Self::packet_modifier( let (repair_receiver, repair_handler) = Self::packet_modifier(

View File

@ -343,7 +343,7 @@ pub fn initialize_state(
let GenesisConfigInfo { let GenesisConfigInfo {
mut genesis_config, mut genesis_config,
mint_keypair, mint_keypair,
voting_keypair: _, ..
} = create_genesis_config_with_vote_accounts( } = create_genesis_config_with_vote_accounts(
1_000_000_000, 1_000_000_000,
&validator_keypairs, &validator_keypairs,

View File

@ -10,6 +10,14 @@ pub struct DataBudget {
} }
impl DataBudget { impl DataBudget {
/// Create a data budget with max bytes, used for tests
pub fn restricted() -> Self {
Self {
bytes: AtomicUsize::default(),
last_timestamp_ms: AtomicU64::new(u64::MAX),
}
}
// If there are enough bytes in the budget, consumes from // If there are enough bytes in the budget, consumes from
// the budget and returns true. Otherwise returns false. // the budget and returns true. Otherwise returns false.
#[must_use] #[must_use]

View File

@ -773,6 +773,7 @@ impl ProgramTest {
genesis_config, genesis_config,
mint_keypair, mint_keypair,
voting_keypair, voting_keypair,
validator_pubkey: bootstrap_validator_pubkey,
}, },
) )
} }

View File

@ -91,11 +91,7 @@ mod test {
&validator_vote_keypairs1, &validator_vote_keypairs1,
&validator_vote_keypairs2, &validator_vote_keypairs2,
]; ];
let GenesisConfigInfo { let GenesisConfigInfo { genesis_config, .. } = create_genesis_config_with_vote_accounts(
genesis_config,
mint_keypair: _,
voting_keypair: _,
} = create_genesis_config_with_vote_accounts(
1_000_000_000, 1_000_000_000,
&validator_keypairs, &validator_keypairs,
vec![10_000; 3], vec![10_000; 3],

View File

@ -6302,6 +6302,7 @@ pub mod tests {
mut genesis_config, mut genesis_config,
mint_keypair, mint_keypair,
voting_keypair, voting_keypair,
..
} = create_genesis_config(TEST_MINT_LAMPORTS); } = create_genesis_config(TEST_MINT_LAMPORTS);
genesis_config.rent.lamports_per_byte_year = 50; genesis_config.rent.lamports_per_byte_year = 50;

View File

@ -14300,11 +14300,7 @@ pub(crate) mod tests {
let validator_vote_keypairs0 = ValidatorVoteKeypairs::new_rand(); let validator_vote_keypairs0 = ValidatorVoteKeypairs::new_rand();
let validator_vote_keypairs1 = ValidatorVoteKeypairs::new_rand(); let validator_vote_keypairs1 = ValidatorVoteKeypairs::new_rand();
let validator_keypairs = vec![&validator_vote_keypairs0, &validator_vote_keypairs1]; let validator_keypairs = vec![&validator_vote_keypairs0, &validator_vote_keypairs1];
let GenesisConfigInfo { let GenesisConfigInfo { genesis_config, .. } = create_genesis_config_with_vote_accounts(
genesis_config,
mint_keypair: _,
voting_keypair: _,
} = create_genesis_config_with_vote_accounts(
1_000_000_000, 1_000_000_000,
&validator_keypairs, &validator_keypairs,
vec![10_000; 2], vec![10_000; 2],

View File

@ -601,8 +601,8 @@ mod tests {
let leader_keypair = Keypair::new(); let leader_keypair = Keypair::new();
let GenesisConfigInfo { let GenesisConfigInfo {
mut genesis_config, mut genesis_config,
mint_keypair: _,
voting_keypair, voting_keypair,
..
} = create_genesis_config_with_leader(10_000, &leader_keypair.pubkey(), 1_000); } = create_genesis_config_with_leader(10_000, &leader_keypair.pubkey(), 1_000);
let slots_in_epoch = 32; let slots_in_epoch = 32;
genesis_config.epoch_schedule = EpochSchedule::new(slots_in_epoch); genesis_config.epoch_schedule = EpochSchedule::new(slots_in_epoch);

View File

@ -52,6 +52,7 @@ pub struct GenesisConfigInfo {
pub genesis_config: GenesisConfig, pub genesis_config: GenesisConfig,
pub mint_keypair: Keypair, pub mint_keypair: Keypair,
pub voting_keypair: Keypair, pub voting_keypair: Keypair,
pub validator_pubkey: Pubkey,
} }
pub fn create_genesis_config(mint_lamports: u64) -> GenesisConfigInfo { pub fn create_genesis_config(mint_lamports: u64) -> GenesisConfigInfo {
@ -84,10 +85,11 @@ pub fn create_genesis_config_with_vote_accounts_and_cluster_type(
let voting_keypair = let voting_keypair =
Keypair::from_bytes(&voting_keypairs[0].borrow().vote_keypair.to_bytes()).unwrap(); Keypair::from_bytes(&voting_keypairs[0].borrow().vote_keypair.to_bytes()).unwrap();
let validator_pubkey = voting_keypairs[0].borrow().node_keypair.pubkey();
let genesis_config = create_genesis_config_with_leader_ex( let genesis_config = create_genesis_config_with_leader_ex(
mint_lamports, mint_lamports,
&mint_keypair.pubkey(), &mint_keypair.pubkey(),
&voting_keypairs[0].borrow().node_keypair.pubkey(), &validator_pubkey,
&voting_keypairs[0].borrow().vote_keypair.pubkey(), &voting_keypairs[0].borrow().vote_keypair.pubkey(),
&voting_keypairs[0].borrow().stake_keypair.pubkey(), &voting_keypairs[0].borrow().stake_keypair.pubkey(),
stakes[0], stakes[0],
@ -102,6 +104,7 @@ pub fn create_genesis_config_with_vote_accounts_and_cluster_type(
genesis_config, genesis_config,
mint_keypair, mint_keypair,
voting_keypair, voting_keypair,
validator_pubkey,
}; };
for (validator_voting_keypairs, stake) in voting_keypairs[1..].iter().zip(&stakes[1..]) { for (validator_voting_keypairs, stake) in voting_keypairs[1..].iter().zip(&stakes[1..]) {
@ -159,6 +162,7 @@ pub fn create_genesis_config_with_leader(
genesis_config, genesis_config,
mint_keypair, mint_keypair,
voting_keypair, voting_keypair,
validator_pubkey: *validator_pubkey,
} }
} }

View File

@ -18,7 +18,7 @@ pub const PACKET_DATA_SIZE: usize = 1280 - 40 - 8;
#[repr(C)] #[repr(C)]
pub struct Meta { pub struct Meta {
pub size: usize, pub size: usize,
pub forward: bool, pub forwarded: bool,
pub repair: bool, pub repair: bool,
pub discard: bool, pub discard: bool,
pub addr: [u16; 8], pub addr: [u16; 8],