2022-07-05 21:24:58 -07:00
|
|
|
use {
|
2022-09-29 14:33:40 -07:00
|
|
|
crate::immutable_deserialized_packet::ImmutableDeserializedPacket,
|
2022-07-05 21:24:58 -07:00
|
|
|
solana_perf::packet::Packet,
|
|
|
|
solana_runtime::{
|
|
|
|
block_cost_limits,
|
2022-12-01 16:25:09 -08:00
|
|
|
cost_model::CostModel,
|
2022-07-05 21:24:58 -07:00
|
|
|
cost_tracker::{CostTracker, CostTrackerError},
|
|
|
|
},
|
2022-12-01 16:25:09 -08:00
|
|
|
solana_sdk::{feature_set::FeatureSet, transaction::SanitizedTransaction},
|
2022-09-30 10:07:48 -07:00
|
|
|
std::sync::Arc,
|
2022-07-05 21:24:58 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
/// `ForwardBatch` to have half of default cost_tracker limits, as smaller batch
|
|
|
|
/// allows better granularity in composing forwarding transactions; e.g.,
|
|
|
|
/// transactions in each batch are potentially more evenly distributed across accounts.
|
|
|
|
const FORWARDED_BLOCK_COMPUTE_RATIO: u32 = 2;
|
|
|
|
/// this number divided by`FORWARDED_BLOCK_COMPUTE_RATIO` is the total blocks to forward.
|
|
|
|
/// To accommodate transactions without `compute_budget` instruction, which will
|
|
|
|
/// have default 200_000 compute units, it has 100 batches as default to forward
|
|
|
|
/// up to 12_000 such transaction. (120 such transactions fill up a batch, 100
|
|
|
|
/// batches allows 12_000 transactions)
|
|
|
|
const DEFAULT_NUMBER_OF_BATCHES: u32 = 100;
|
|
|
|
|
|
|
|
/// `ForwardBatch` represents one forwardable batch of transactions with a
|
|
|
|
/// limited number of total compute units
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct ForwardBatch {
|
|
|
|
cost_tracker: CostTracker,
|
|
|
|
// `forwardable_packets` keeps forwardable packets in a vector in its
|
|
|
|
// original fee prioritized order
|
2022-09-30 10:07:48 -07:00
|
|
|
forwardable_packets: Vec<Arc<ImmutableDeserializedPacket>>,
|
2022-07-05 21:24:58 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for ForwardBatch {
|
|
|
|
/// default ForwardBatch has cost_tracker with default limits
|
|
|
|
fn default() -> Self {
|
|
|
|
Self::new(1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ForwardBatch {
|
|
|
|
/// `ForwardBatch` keeps forwardable packets in a vector in its original fee prioritized order,
|
|
|
|
/// Number of packets are limited by `cost_tracker` with customized `limit_ratio` to lower
|
|
|
|
/// (when `limit_ratio` > 1) `cost_tracker`'s default limits.
|
|
|
|
/// Lower limits yield smaller batch for forwarding.
|
|
|
|
fn new(limit_ratio: u32) -> Self {
|
|
|
|
let mut cost_tracker = CostTracker::default();
|
|
|
|
cost_tracker.set_limits(
|
|
|
|
block_cost_limits::MAX_WRITABLE_ACCOUNT_UNITS.saturating_div(limit_ratio as u64),
|
|
|
|
block_cost_limits::MAX_BLOCK_UNITS.saturating_div(limit_ratio as u64),
|
|
|
|
block_cost_limits::MAX_VOTE_UNITS.saturating_div(limit_ratio as u64),
|
|
|
|
);
|
|
|
|
Self {
|
|
|
|
cost_tracker,
|
|
|
|
forwardable_packets: Vec::default(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn try_add(
|
|
|
|
&mut self,
|
2022-12-01 16:25:09 -08:00
|
|
|
sanitized_transaction: &SanitizedTransaction,
|
2022-09-30 10:07:48 -07:00
|
|
|
immutable_packet: Arc<ImmutableDeserializedPacket>,
|
2022-12-01 16:25:09 -08:00
|
|
|
feature_set: &FeatureSet,
|
2022-07-05 21:24:58 -07:00
|
|
|
) -> Result<u64, CostTrackerError> {
|
2022-12-01 16:25:09 -08:00
|
|
|
let tx_cost = CostModel::calculate_cost(sanitized_transaction, feature_set);
|
|
|
|
let res = self.cost_tracker.try_add(&tx_cost);
|
2022-07-05 21:24:58 -07:00
|
|
|
if res.is_ok() {
|
|
|
|
self.forwardable_packets.push(immutable_packet);
|
|
|
|
}
|
|
|
|
res
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_forwardable_packets(&self) -> impl Iterator<Item = &Packet> {
|
|
|
|
self.forwardable_packets
|
|
|
|
.iter()
|
|
|
|
.map(|immutable_packet| immutable_packet.original_packet())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn len(&self) -> usize {
|
|
|
|
self.forwardable_packets.len()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn is_empty(&self) -> bool {
|
|
|
|
self.forwardable_packets.is_empty()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// To avoid forward queue being saturated by transactions for single hot account,
|
|
|
|
/// the forwarder will group and send prioritized transactions by account limit
|
|
|
|
/// to allow transactions on non-congested accounts to be forwarded alongside higher fee
|
|
|
|
/// transactions that saturate those highly demanded accounts.
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct ForwardPacketBatchesByAccounts {
|
|
|
|
// Forwardable packets are staged in number of batches, each batch is limited
|
|
|
|
// by cost_tracker on both account limit and block limits. Those limits are
|
|
|
|
// set as `limit_ratio` of regular block limits to facilitate quicker iteration.
|
|
|
|
forward_batches: Vec<ForwardBatch>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ForwardPacketBatchesByAccounts {
|
2022-09-29 14:33:40 -07:00
|
|
|
pub fn new_with_default_batch_limits() -> Self {
|
|
|
|
Self::new(FORWARDED_BLOCK_COMPUTE_RATIO, DEFAULT_NUMBER_OF_BATCHES)
|
2022-07-05 21:24:58 -07:00
|
|
|
}
|
|
|
|
|
2022-09-29 14:33:40 -07:00
|
|
|
pub fn new(limit_ratio: u32, number_of_batches: u32) -> Self {
|
2022-07-05 21:24:58 -07:00
|
|
|
let forward_batches = (0..number_of_batches)
|
|
|
|
.map(|_| ForwardBatch::new(limit_ratio))
|
|
|
|
.collect();
|
2022-12-06 10:13:01 -08:00
|
|
|
Self { forward_batches }
|
2022-07-05 21:24:58 -07:00
|
|
|
}
|
|
|
|
|
2022-12-06 10:13:01 -08:00
|
|
|
/// packets are filled into first available 'batch' that have space to fit it.
|
2022-09-29 14:33:40 -07:00
|
|
|
pub fn try_add_packet(
|
2022-07-05 21:24:58 -07:00
|
|
|
&mut self,
|
2022-12-01 16:25:09 -08:00
|
|
|
sanitized_transaction: &SanitizedTransaction,
|
2022-09-30 10:07:48 -07:00
|
|
|
immutable_packet: Arc<ImmutableDeserializedPacket>,
|
2022-12-01 16:25:09 -08:00
|
|
|
feature_set: &FeatureSet,
|
2022-07-05 21:24:58 -07:00
|
|
|
) -> bool {
|
|
|
|
for forward_batch in self.forward_batches.iter_mut() {
|
|
|
|
if forward_batch
|
2022-12-01 16:25:09 -08:00
|
|
|
.try_add(sanitized_transaction, immutable_packet.clone(), feature_set)
|
2022-07-05 21:24:58 -07:00
|
|
|
.is_ok()
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
false
|
|
|
|
}
|
2022-12-06 10:13:01 -08:00
|
|
|
|
|
|
|
pub fn iter_batches(&self) -> impl Iterator<Item = &ForwardBatch> {
|
|
|
|
self.forward_batches.iter()
|
|
|
|
}
|
2022-07-05 21:24:58 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use {
|
|
|
|
super::*,
|
2022-10-20 14:10:48 -07:00
|
|
|
crate::unprocessed_packet_batches::DeserializedPacket,
|
2022-12-01 16:25:09 -08:00
|
|
|
solana_runtime::{
|
|
|
|
bank::Bank,
|
|
|
|
genesis_utils::{create_genesis_config, GenesisConfigInfo},
|
|
|
|
transaction_priority_details::TransactionPriorityDetails,
|
|
|
|
},
|
2022-09-29 14:33:40 -07:00
|
|
|
solana_sdk::{
|
2022-12-01 16:25:09 -08:00
|
|
|
feature_set::FeatureSet, hash::Hash, pubkey::Pubkey, signature::Keypair,
|
|
|
|
system_transaction,
|
2022-07-05 21:24:58 -07:00
|
|
|
},
|
2022-09-29 14:33:40 -07:00
|
|
|
std::sync::Arc,
|
2022-07-05 21:24:58 -07:00
|
|
|
};
|
|
|
|
|
2022-12-01 16:25:09 -08:00
|
|
|
fn test_setup() -> (Keypair, Hash) {
|
|
|
|
solana_logger::setup();
|
|
|
|
let GenesisConfigInfo {
|
|
|
|
genesis_config,
|
|
|
|
mint_keypair,
|
|
|
|
..
|
|
|
|
} = create_genesis_config(10);
|
|
|
|
let bank = Arc::new(Bank::new_no_wallclock_throttle_for_tests(&genesis_config));
|
|
|
|
let start_hash = bank.last_blockhash();
|
|
|
|
(mint_keypair, start_hash)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// build test transaction, return corresponding sanitized_transaction and deserialized_packet,
|
|
|
|
/// and the batch limit_ratio that would only allow one transaction per bucket.
|
|
|
|
fn build_test_transaction_and_packet(
|
2022-07-05 21:24:58 -07:00
|
|
|
priority: u64,
|
2022-09-29 14:33:40 -07:00
|
|
|
write_to_account: &Pubkey,
|
2022-12-01 16:25:09 -08:00
|
|
|
) -> (SanitizedTransaction, DeserializedPacket, u32) {
|
|
|
|
let (mint_keypair, start_hash) = test_setup();
|
|
|
|
let transaction =
|
|
|
|
system_transaction::transfer(&mint_keypair, write_to_account, 2, start_hash);
|
|
|
|
let sanitized_transaction =
|
|
|
|
SanitizedTransaction::from_transaction_for_tests(transaction.clone());
|
|
|
|
let tx_cost = CostModel::calculate_cost(&sanitized_transaction, &FeatureSet::all_enabled());
|
|
|
|
let cost = tx_cost.sum();
|
|
|
|
let deserialized_packet = DeserializedPacket::new_with_priority_details(
|
|
|
|
Packet::from_data(None, transaction).unwrap(),
|
2022-07-05 21:24:58 -07:00
|
|
|
TransactionPriorityDetails {
|
|
|
|
priority,
|
2022-12-01 16:25:09 -08:00
|
|
|
compute_unit_limit: cost,
|
2022-07-05 21:24:58 -07:00
|
|
|
},
|
|
|
|
)
|
2022-12-01 16:25:09 -08:00
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
// set limit ratio so each batch can only have one test transaction
|
|
|
|
let limit_ratio: u32 =
|
|
|
|
((block_cost_limits::MAX_WRITABLE_ACCOUNT_UNITS - cost + 1) / cost) as u32;
|
|
|
|
(sanitized_transaction, deserialized_packet, limit_ratio)
|
2022-07-05 21:24:58 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_try_add_to_forward_batch() {
|
2022-12-01 16:25:09 -08:00
|
|
|
let (tx, packet, limit_ratio) =
|
|
|
|
build_test_transaction_and_packet(0u64, &Pubkey::new_unique());
|
2022-07-05 21:24:58 -07:00
|
|
|
let mut forward_batch = ForwardBatch::new(limit_ratio);
|
|
|
|
|
2022-12-01 16:25:09 -08:00
|
|
|
// Assert first packet will be added to forwarding buffer
|
2022-07-05 21:24:58 -07:00
|
|
|
assert!(forward_batch
|
|
|
|
.try_add(
|
2022-12-01 16:25:09 -08:00
|
|
|
&tx,
|
|
|
|
packet.immutable_section().clone(),
|
|
|
|
&FeatureSet::all_enabled(),
|
2022-07-05 21:24:58 -07:00
|
|
|
)
|
|
|
|
.is_ok());
|
|
|
|
assert_eq!(1, forward_batch.forwardable_packets.len());
|
2022-12-01 16:25:09 -08:00
|
|
|
|
|
|
|
// Assert second copy of same packet will hit write account limit, therefore
|
|
|
|
// not be added to forwarding buffer
|
2022-07-05 21:24:58 -07:00
|
|
|
assert!(forward_batch
|
|
|
|
.try_add(
|
2022-12-01 16:25:09 -08:00
|
|
|
&tx,
|
|
|
|
packet.immutable_section().clone(),
|
|
|
|
&FeatureSet::all_enabled(),
|
2022-07-05 21:24:58 -07:00
|
|
|
)
|
|
|
|
.is_err());
|
|
|
|
assert_eq!(1, forward_batch.forwardable_packets.len());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2022-12-06 10:13:01 -08:00
|
|
|
fn test_try_add_packeti_to_multiple_batches() {
|
2022-12-01 16:25:09 -08:00
|
|
|
// setup two transactions, one has high priority that writes to hot account, the
|
|
|
|
// other write to non-contentious account with no priority
|
|
|
|
let hot_account = solana_sdk::pubkey::new_rand();
|
|
|
|
let other_account = solana_sdk::pubkey::new_rand();
|
|
|
|
let (tx_high_priority, packet_high_priority, limit_ratio) =
|
|
|
|
build_test_transaction_and_packet(10, &hot_account);
|
|
|
|
let (tx_low_priority, packet_low_priority, _) =
|
|
|
|
build_test_transaction_and_packet(0, &other_account);
|
2022-07-05 21:24:58 -07:00
|
|
|
|
2022-12-01 16:25:09 -08:00
|
|
|
// setup forwarding with 2 buckets, each only allow one transaction
|
|
|
|
let number_of_batches = 2;
|
2022-09-29 14:33:40 -07:00
|
|
|
let mut forward_packet_batches_by_accounts =
|
|
|
|
ForwardPacketBatchesByAccounts::new(limit_ratio, number_of_batches);
|
2022-07-05 21:24:58 -07:00
|
|
|
|
2022-12-01 16:25:09 -08:00
|
|
|
// Assert initially both batches are empty
|
2022-07-05 21:24:58 -07:00
|
|
|
{
|
|
|
|
let mut batches = forward_packet_batches_by_accounts.iter_batches();
|
|
|
|
assert_eq!(0, batches.next().unwrap().len());
|
|
|
|
assert_eq!(0, batches.next().unwrap().len());
|
|
|
|
assert!(batches.next().is_none());
|
|
|
|
}
|
|
|
|
|
2022-12-01 16:25:09 -08:00
|
|
|
// Assert one high-priority packet will be added to 1st bucket successfully
|
2022-07-05 21:24:58 -07:00
|
|
|
{
|
2022-12-06 10:13:01 -08:00
|
|
|
assert!(forward_packet_batches_by_accounts.try_add_packet(
|
2022-12-01 16:25:09 -08:00
|
|
|
&tx_high_priority,
|
2022-07-05 21:24:58 -07:00
|
|
|
packet_high_priority.immutable_section().clone(),
|
2022-12-01 16:25:09 -08:00
|
|
|
&FeatureSet::all_enabled(),
|
|
|
|
));
|
2022-07-05 21:24:58 -07:00
|
|
|
let mut batches = forward_packet_batches_by_accounts.iter_batches();
|
|
|
|
assert_eq!(1, batches.next().unwrap().len());
|
|
|
|
assert_eq!(0, batches.next().unwrap().len());
|
|
|
|
assert!(batches.next().is_none());
|
|
|
|
}
|
|
|
|
|
2022-12-01 16:25:09 -08:00
|
|
|
// Assert second high-priority packet will not fit in first bucket, but will
|
2022-12-06 10:13:01 -08:00
|
|
|
// be added to 2nd bucket
|
2022-07-05 21:24:58 -07:00
|
|
|
{
|
2022-12-06 10:13:01 -08:00
|
|
|
assert!(forward_packet_batches_by_accounts.try_add_packet(
|
2022-12-01 16:25:09 -08:00
|
|
|
&tx_high_priority,
|
2022-07-05 21:24:58 -07:00
|
|
|
packet_high_priority.immutable_section().clone(),
|
2022-12-01 16:25:09 -08:00
|
|
|
&FeatureSet::all_enabled(),
|
|
|
|
));
|
2022-07-05 21:24:58 -07:00
|
|
|
let mut batches = forward_packet_batches_by_accounts.iter_batches();
|
|
|
|
assert_eq!(1, batches.next().unwrap().len());
|
|
|
|
assert_eq!(1, batches.next().unwrap().len());
|
|
|
|
}
|
|
|
|
|
2022-12-06 10:13:01 -08:00
|
|
|
// Assert 3rd high-priority packet can not added since both buckets would
|
2022-12-01 16:25:09 -08:00
|
|
|
// exceed hot-account limit
|
2022-07-05 21:24:58 -07:00
|
|
|
{
|
2022-12-06 10:13:01 -08:00
|
|
|
assert!(!forward_packet_batches_by_accounts.try_add_packet(
|
2022-12-01 16:25:09 -08:00
|
|
|
&tx_high_priority,
|
2022-07-05 21:24:58 -07:00
|
|
|
packet_high_priority.immutable_section().clone(),
|
2022-12-01 16:25:09 -08:00
|
|
|
&FeatureSet::all_enabled(),
|
|
|
|
));
|
2022-07-05 21:24:58 -07:00
|
|
|
let mut batches = forward_packet_batches_by_accounts.iter_batches();
|
|
|
|
assert_eq!(1, batches.next().unwrap().len());
|
|
|
|
assert_eq!(1, batches.next().unwrap().len());
|
|
|
|
assert!(batches.next().is_none());
|
|
|
|
}
|
|
|
|
|
2022-12-01 16:25:09 -08:00
|
|
|
// Assert lower priority packet will be successfully added to first bucket
|
|
|
|
// since non-contentious account is still free
|
2022-07-05 21:24:58 -07:00
|
|
|
{
|
2022-12-06 10:13:01 -08:00
|
|
|
assert!(forward_packet_batches_by_accounts.try_add_packet(
|
2022-12-01 16:25:09 -08:00
|
|
|
&tx_low_priority,
|
2022-07-05 21:24:58 -07:00
|
|
|
packet_low_priority.immutable_section().clone(),
|
2022-12-01 16:25:09 -08:00
|
|
|
&FeatureSet::all_enabled(),
|
|
|
|
));
|
2022-07-05 21:24:58 -07:00
|
|
|
let mut batches = forward_packet_batches_by_accounts.iter_batches();
|
|
|
|
assert_eq!(2, batches.next().unwrap().len());
|
|
|
|
assert_eq!(1, batches.next().unwrap().len());
|
|
|
|
assert!(batches.next().is_none());
|
|
|
|
}
|
|
|
|
}
|
2022-09-29 14:33:40 -07:00
|
|
|
|
|
|
|
#[test]
|
2022-12-06 10:13:01 -08:00
|
|
|
fn test_try_add_packet_to_single_batch() {
|
2022-12-01 16:25:09 -08:00
|
|
|
let (tx, packet, limit_ratio) =
|
|
|
|
build_test_transaction_and_packet(10, &solana_sdk::pubkey::new_rand());
|
2022-09-29 14:33:40 -07:00
|
|
|
let number_of_batches = 1;
|
|
|
|
let mut forward_packet_batches_by_accounts =
|
|
|
|
ForwardPacketBatchesByAccounts::new(limit_ratio, number_of_batches);
|
|
|
|
|
2022-12-01 16:25:09 -08:00
|
|
|
// Assert initially batch is empty, and accepting new packets
|
2022-09-29 14:33:40 -07:00
|
|
|
{
|
|
|
|
let mut batches = forward_packet_batches_by_accounts.iter_batches();
|
|
|
|
assert_eq!(0, batches.next().unwrap().len());
|
|
|
|
assert!(batches.next().is_none());
|
|
|
|
}
|
|
|
|
|
2022-12-01 16:25:09 -08:00
|
|
|
// Assert can successfully add first packet to forwarding buffer
|
2022-09-29 14:33:40 -07:00
|
|
|
{
|
2022-12-01 16:25:09 -08:00
|
|
|
assert!(forward_packet_batches_by_accounts.try_add_packet(
|
|
|
|
&tx,
|
|
|
|
packet.immutable_section().clone(),
|
|
|
|
&FeatureSet::all_enabled()
|
|
|
|
));
|
2022-09-29 14:33:40 -07:00
|
|
|
|
|
|
|
let mut batches = forward_packet_batches_by_accounts.iter_batches();
|
|
|
|
assert_eq!(1, batches.next().unwrap().len());
|
|
|
|
}
|
|
|
|
|
2022-12-01 16:25:09 -08:00
|
|
|
// Assert cannot add same packet to forwarding buffer again, due to reached account limit;
|
2022-09-29 14:33:40 -07:00
|
|
|
{
|
2022-12-01 16:25:09 -08:00
|
|
|
assert!(!forward_packet_batches_by_accounts.try_add_packet(
|
|
|
|
&tx,
|
|
|
|
packet.immutable_section().clone(),
|
|
|
|
&FeatureSet::all_enabled()
|
|
|
|
));
|
2022-09-29 14:33:40 -07:00
|
|
|
|
|
|
|
let mut batches = forward_packet_batches_by_accounts.iter_batches();
|
|
|
|
assert_eq!(1, batches.next().unwrap().len());
|
|
|
|
}
|
|
|
|
|
2022-12-06 10:13:01 -08:00
|
|
|
// Assert can still add non-contentious packet to same batch
|
2022-09-29 14:33:40 -07:00
|
|
|
{
|
2022-12-01 16:25:09 -08:00
|
|
|
// build a small packet to a non-contentious account with high priority
|
|
|
|
let (tx2, packet2, _) =
|
|
|
|
build_test_transaction_and_packet(100, &solana_sdk::pubkey::new_rand());
|
|
|
|
|
2022-12-06 10:13:01 -08:00
|
|
|
assert!(forward_packet_batches_by_accounts.try_add_packet(
|
2022-12-01 16:25:09 -08:00
|
|
|
&tx2,
|
|
|
|
packet2.immutable_section().clone(),
|
|
|
|
&FeatureSet::all_enabled()
|
|
|
|
));
|
2022-09-29 14:33:40 -07:00
|
|
|
|
|
|
|
let mut batches = forward_packet_batches_by_accounts.iter_batches();
|
2022-12-06 10:13:01 -08:00
|
|
|
assert_eq!(2, batches.next().unwrap().len());
|
2022-09-29 14:33:40 -07:00
|
|
|
}
|
|
|
|
}
|
2022-07-05 21:24:58 -07:00
|
|
|
}
|