From 6a70b6c3906c9a294177c3d1d5b7b5e525c57208 Mon Sep 17 00:00:00 2001 From: Andrew Fitzgerald Date: Wed, 16 Oct 2024 08:58:56 -0500 Subject: [PATCH] TransactionCost - hold transaction reference (#3162) --- Cargo.lock | 1 + core/Cargo.toml | 5 +- .../forward_packet_batches_by_accounts.rs | 36 ++- core/src/banking_stage/qos_service.rs | 46 +++- cost-model/Cargo.toml | 3 + cost-model/benches/cost_tracker.rs | 45 +++- cost-model/src/cost_model.rs | 232 ++++++++-------- cost-model/src/cost_tracker.rs | 255 +++++++++--------- cost-model/src/transaction_cost.rs | 165 +++++++----- 9 files changed, 433 insertions(+), 355 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2acd2130e695de..0ce1971cb71b8e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6495,6 +6495,7 @@ dependencies = [ "rand 0.8.5", "solana-builtins-default-costs", "solana-compute-budget", + "solana-cost-model", "solana-feature-set", "solana-frozen-abi", "solana-frozen-abi-macro", diff --git a/core/Cargo.toml b/core/Cargo.toml index 4d0797908627f5..344d0360d7337c 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -105,6 +105,7 @@ serde_json = { workspace = true } serial_test = { workspace = true } # See order-crates-for-publishing.py for using this unusual `path = "."` solana-core = { path = ".", features = ["dev-context-only-utils"] } +solana-cost-model = { workspace = true, features = ["dev-context-only-utils"] } solana-ledger = { workspace = true, features = ["dev-context-only-utils"] } solana-logger = { workspace = true } solana-poh = { workspace = true, features = ["dev-context-only-utils"] } @@ -123,9 +124,7 @@ test-case = { workspace = true } sysctl = { workspace = true } [features] -dev-context-only-utils = [ - "solana-runtime/dev-context-only-utils", -] +dev-context-only-utils = ["solana-runtime/dev-context-only-utils"] frozen-abi = [ "dep:solana-frozen-abi", "dep:solana-frozen-abi-macro", diff --git a/core/src/banking_stage/forward_packet_batches_by_accounts.rs b/core/src/banking_stage/forward_packet_batches_by_accounts.rs index 1d86cfb9753b1b..67b323c2876a18 100644 --- a/core/src/banking_stage/forward_packet_batches_by_accounts.rs +++ b/core/src/banking_stage/forward_packet_batches_by_accounts.rs @@ -9,6 +9,7 @@ use { solana_feature_set::FeatureSet, solana_perf::packet::Packet, solana_sdk::transaction::SanitizedTransaction, + solana_svm_transaction::svm_message::SVMMessage, std::sync::Arc, }; @@ -146,7 +147,7 @@ impl ForwardPacketBatchesByAccounts { // put into batch #3 to satisfy all batch limits. fn get_batch_index_by_updated_costs( &self, - tx_cost: &TransactionCost, + tx_cost: &TransactionCost, updated_costs: &UpdatedCosts, ) -> usize { let Some(batch_index_by_block_limit) = @@ -170,11 +171,14 @@ mod tests { use { super::*, crate::banking_stage::unprocessed_packet_batches::DeserializedPacket, - solana_cost_model::transaction_cost::UsageCostDetails, + solana_cost_model::transaction_cost::{UsageCostDetails, WritableKeysTransaction}, solana_feature_set::FeatureSet, solana_sdk::{ - compute_budget::ComputeBudgetInstruction, message::Message, pubkey::Pubkey, - system_instruction, transaction::Transaction, + compute_budget::ComputeBudgetInstruction, + message::{Message, TransactionSignatureDetails}, + pubkey::Pubkey, + system_instruction, + transaction::Transaction, }, }; @@ -206,6 +210,21 @@ mod tests { (sanitized_transaction, deserialized_packet, limit_ratio) } + fn zero_transaction_cost() -> TransactionCost<'static, WritableKeysTransaction> { + static DUMMY_TRANSACTION: WritableKeysTransaction = WritableKeysTransaction(vec![]); + + TransactionCost::Transaction(UsageCostDetails { + transaction: &DUMMY_TRANSACTION, + signature_cost: 0, + write_lock_cost: 0, + data_bytes_cost: 0, + programs_execution_cost: 0, + loaded_accounts_data_size_cost: 0, + allocated_accounts_data_size: 0, + signature_details: TransactionSignatureDetails::new(0, 0, 0), + }) + } + #[test] fn test_try_add_packet_to_multiple_batches() { // setup two transactions, one has high priority that writes to hot account, the @@ -351,8 +370,9 @@ mod tests { ForwardPacketBatchesByAccounts::new_with_default_batch_limits(); forward_packet_batches_by_accounts.batch_vote_limit = test_cost + 1; + let dummy_transaction = WritableKeysTransaction(vec![]); let transaction_cost = TransactionCost::SimpleVote { - writable_accounts: vec![], + transaction: &dummy_transaction, }; assert_eq!( 0, @@ -382,7 +402,7 @@ mod tests { ForwardPacketBatchesByAccounts::new_with_default_batch_limits(); forward_packet_batches_by_accounts.batch_block_limit = test_cost + 1; - let transaction_cost = TransactionCost::Transaction(UsageCostDetails::default()); + let transaction_cost = zero_transaction_cost(); assert_eq!( 0, forward_packet_batches_by_accounts.get_batch_index_by_updated_costs( @@ -411,7 +431,7 @@ mod tests { ForwardPacketBatchesByAccounts::new_with_default_batch_limits(); forward_packet_batches_by_accounts.batch_account_limit = test_cost + 1; - let transaction_cost = TransactionCost::Transaction(UsageCostDetails::default()); + let transaction_cost = zero_transaction_cost(); assert_eq!( 0, forward_packet_batches_by_accounts.get_batch_index_by_updated_costs( @@ -445,7 +465,7 @@ mod tests { forward_packet_batches_by_accounts.batch_vote_limit = test_cost / 2 + 1; forward_packet_batches_by_accounts.batch_account_limit = test_cost / 3 + 1; - let transaction_cost = TransactionCost::Transaction(UsageCostDetails::default()); + let transaction_cost = zero_transaction_cost(); assert_eq!( 2, forward_packet_batches_by_accounts.get_batch_index_by_updated_costs( diff --git a/core/src/banking_stage/qos_service.rs b/core/src/banking_stage/qos_service.rs index 21e19be6f0ec52..0d9b2f02a32ee9 100644 --- a/core/src/banking_stage/qos_service.rs +++ b/core/src/banking_stage/qos_service.rs @@ -16,6 +16,7 @@ use { saturating_add_assign, transaction::{self, SanitizedTransaction, TransactionError}, }, + solana_svm_transaction::svm_message::SVMMessage, std::sync::atomic::{AtomicU64, Ordering}, }; @@ -39,12 +40,15 @@ impl QosService { /// include in the slot, and accumulate costs in the cost tracker. /// Returns a vector of results containing selected transaction costs, and the number of /// transactions that were *NOT* selected. - pub fn select_and_accumulate_transaction_costs( + pub fn select_and_accumulate_transaction_costs<'a>( &self, bank: &Bank, - transactions: &[SanitizedTransaction], + transactions: &'a [SanitizedTransaction], pre_results: impl Iterator>, - ) -> (Vec>, u64) { + ) -> ( + Vec>>, + u64, + ) { let transaction_costs = self.compute_transaction_costs(&bank.feature_set, transactions.iter(), pre_results); let (transactions_qos_cost_results, num_included) = self.select_transactions_per_cost( @@ -71,7 +75,7 @@ impl QosService { feature_set: &FeatureSet, transactions: impl Iterator, pre_results: impl Iterator>, - ) -> Vec> { + ) -> Vec>> { let mut compute_cost_time = Measure::start("compute_cost_time"); let txs_costs: Vec<_> = transactions .zip(pre_results) @@ -95,9 +99,14 @@ impl QosService { fn select_transactions_per_cost<'a>( &self, transactions: impl Iterator, - transactions_costs: impl Iterator>, + transactions_costs: impl Iterator< + Item = transaction::Result>, + >, bank: &Bank, - ) -> (Vec>, usize) { + ) -> ( + Vec>>, + usize, + ) { let mut cost_tracking_time = Measure::start("cost_tracking_time"); let mut cost_tracker = bank.write_cost_tracker().unwrap(); let mut num_included = 0; @@ -153,7 +162,9 @@ impl QosService { /// Removes transaction costs from the cost tracker if not committed or recorded, or /// updates the transaction costs for committed transactions. pub fn remove_or_update_costs<'a>( - transaction_cost_results: impl Iterator>, + transaction_cost_results: impl Iterator< + Item = &'a transaction::Result>, + >, transaction_committed_status: Option<&Vec>, bank: &Bank, ) { @@ -172,7 +183,9 @@ impl QosService { /// For recorded transactions, remove units reserved by uncommitted transaction, or update /// units for committed transactions. fn remove_or_update_recorded_transaction_costs<'a>( - transaction_cost_results: impl Iterator>, + transaction_cost_results: impl Iterator< + Item = &'a transaction::Result>, + >, transaction_committed_status: &Vec, bank: &Bank, ) { @@ -210,7 +223,9 @@ impl QosService { /// Remove reserved units for transaction batch that unsuccessfully recorded. fn remove_unrecorded_transaction_costs<'a>( - transaction_cost_results: impl Iterator>, + transaction_cost_results: impl Iterator< + Item = &'a transaction::Result>, + >, bank: &Bank, ) { let mut cost_tracker = bank.write_cost_tracker().unwrap(); @@ -326,8 +341,8 @@ impl QosService { // rollup transaction cost details, eg signature_cost, write_lock_cost, data_bytes_cost and // execution_cost from the batch of transactions selected for block. - fn accumulate_batched_transaction_costs<'a>( - transactions_costs: impl Iterator>, + fn accumulate_batched_transaction_costs<'a, Tx: SVMMessage + 'a>( + transactions_costs: impl Iterator>>, ) -> BatchedTransactionDetails { let mut batched_transaction_details = BatchedTransactionDetails::default(); transactions_costs.for_each(|cost| match cost { @@ -609,10 +624,11 @@ mod tests { use { super::*, itertools::Itertools, - solana_cost_model::transaction_cost::UsageCostDetails, + solana_cost_model::transaction_cost::{UsageCostDetails, WritableKeysTransaction}, solana_runtime::genesis_utils::{create_genesis_config, GenesisConfigInfo}, solana_sdk::{ hash::Hash, + message::TransactionSignatureDetails, signature::{Keypair, Signer}, system_transaction, }, @@ -935,15 +951,19 @@ mod tests { let programs_execution_cost = 10; let num_txs = 4; + let dummy_transaction = WritableKeysTransaction(vec![]); let tx_cost_results: Vec<_> = (0..num_txs) .map(|n| { if n % 2 == 0 { Ok(TransactionCost::Transaction(UsageCostDetails { + transaction: &dummy_transaction, signature_cost, write_lock_cost, data_bytes_cost, programs_execution_cost, - ..UsageCostDetails::default() + loaded_accounts_data_size_cost: 0, + allocated_accounts_data_size: 0, + signature_details: TransactionSignatureDetails::new(0, 0, 0), })) } else { Err(TransactionError::WouldExceedMaxBlockCostLimit) diff --git a/cost-model/Cargo.toml b/cost-model/Cargo.toml index 54564e1e0dd9ca..56948b20462a27 100644 --- a/cost-model/Cargo.toml +++ b/cost-model/Cargo.toml @@ -35,6 +35,8 @@ name = "solana_cost_model" [dev-dependencies] itertools = { workspace = true } rand = "0.8.5" +# See order-crates-for-publishing.py for using this unusual `path = "."` +solana-cost-model = { path = ".", features = ["dev-context-only-utils"] } solana-logger = { workspace = true } solana-sdk = { workspace = true, features = ["dev-context-only-utils"] } solana-system-program = { workspace = true } @@ -45,6 +47,7 @@ test-case = { workspace = true } targets = ["x86_64-unknown-linux-gnu"] [features] +dev-context-only-utils = [] frozen-abi = [ "dep:solana-frozen-abi", "dep:solana-frozen-abi-macro", diff --git a/cost-model/benches/cost_tracker.rs b/cost-model/benches/cost_tracker.rs index 0c41ecc4f37cf3..a7b8b107d09a8d 100644 --- a/cost-model/benches/cost_tracker.rs +++ b/cost-model/benches/cost_tracker.rs @@ -4,15 +4,15 @@ use { itertools::Itertools, solana_cost_model::{ cost_tracker::CostTracker, - transaction_cost::{TransactionCost, UsageCostDetails}, + transaction_cost::{TransactionCost, UsageCostDetails, WritableKeysTransaction}, }, - solana_sdk::pubkey::Pubkey, + solana_sdk::{message::TransactionSignatureDetails, pubkey::Pubkey}, test::Bencher, }; struct BenchSetup { cost_tracker: CostTracker, - tx_costs: Vec, + transactions: Vec, } fn setup(num_transactions: usize, contentious_transactions: bool) -> BenchSetup { @@ -22,36 +22,54 @@ fn setup(num_transactions: usize, contentious_transactions: bool) -> BenchSetup let max_accounts_per_tx = 128; let pubkey = Pubkey::new_unique(); - let tx_costs = (0..num_transactions) + let transactions = (0..num_transactions) .map(|_| { - let mut usage_cost_details = UsageCostDetails::default(); + let mut writable_accounts = Vec::with_capacity(max_accounts_per_tx); (0..max_accounts_per_tx).for_each(|_| { let writable_account_key = if contentious_transactions { pubkey } else { Pubkey::new_unique() }; - usage_cost_details - .writable_accounts - .push(writable_account_key) + writable_accounts.push(writable_account_key) }); - usage_cost_details.programs_execution_cost = 9999; - TransactionCost::Transaction(usage_cost_details) + WritableKeysTransaction(writable_accounts) }) .collect_vec(); BenchSetup { cost_tracker, - tx_costs, + transactions, } } +fn get_costs( + transactions: &[WritableKeysTransaction], +) -> Vec> { + transactions + .iter() + .map(|transaction| { + TransactionCost::Transaction(UsageCostDetails { + transaction, + signature_cost: 0, + write_lock_cost: 0, + data_bytes_cost: 0, + programs_execution_cost: 9999, + loaded_accounts_data_size_cost: 0, + allocated_accounts_data_size: 0, + signature_details: TransactionSignatureDetails::new(0, 0, 0), + }) + }) + .collect_vec() +} + #[bench] fn bench_cost_tracker_non_contentious_transaction(bencher: &mut Bencher) { let BenchSetup { mut cost_tracker, - tx_costs, + transactions, } = setup(1024, false); + let tx_costs = get_costs(&transactions); bencher.iter(|| { for tx_cost in tx_costs.iter() { @@ -67,8 +85,9 @@ fn bench_cost_tracker_non_contentious_transaction(bencher: &mut Bencher) { fn bench_cost_tracker_contentious_transaction(bencher: &mut Bencher) { let BenchSetup { mut cost_tracker, - tx_costs, + transactions, } = setup(1024, true); + let tx_costs = get_costs(&transactions); bencher.iter(|| { for tx_cost in tx_costs.iter() { diff --git a/cost-model/src/cost_model.rs b/cost-model/src/cost_model.rs index c1a36a7ac15e2b..6fb9eee2d86158 100644 --- a/cost-model/src/cost_model.rs +++ b/cost-model/src/cost_model.rs @@ -7,7 +7,6 @@ use { crate::{block_cost_limits::*, transaction_cost::*}, - log::*, solana_builtins_default_costs::BUILTIN_INSTRUCTION_COSTS, solana_compute_budget::compute_budget_limits::{ DEFAULT_HEAP_COST, DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT, MAX_COMPUTE_UNIT_LIMIT, @@ -19,6 +18,7 @@ use { compute_budget::{self, ComputeBudgetInstruction}, fee::FeeStructure, instruction::CompiledInstruction, + message::TransactionSignatureDetails, program_utils::limited_deserialize, pubkey::Pubkey, saturating_add_assign, @@ -42,70 +42,82 @@ enum SystemProgramAccountAllocation { } impl CostModel { - pub fn calculate_cost( - transaction: &SanitizedTransaction, + pub fn calculate_cost<'a>( + transaction: &'a SanitizedTransaction, feature_set: &FeatureSet, - ) -> TransactionCost { + ) -> TransactionCost<'a, SanitizedTransaction> { if transaction.is_simple_vote_transaction() { - TransactionCost::SimpleVote { - writable_accounts: Self::get_writable_accounts(transaction), - } + TransactionCost::SimpleVote { transaction } } else { - let mut tx_cost = UsageCostDetails::new_with_default_capacity(); - - Self::get_signature_cost(&mut tx_cost, transaction, feature_set); - Self::get_write_lock_cost(&mut tx_cost, transaction, feature_set); - Self::get_transaction_cost(&mut tx_cost, transaction, feature_set); - tx_cost.allocated_accounts_data_size = + let (signatures_count_detail, signature_cost) = + Self::get_signature_cost(transaction, feature_set); + let write_lock_cost = Self::get_write_lock_cost(transaction, feature_set); + let (programs_execution_cost, loaded_accounts_data_size_cost, data_bytes_cost) = + Self::get_transaction_cost(transaction, feature_set); + let allocated_accounts_data_size = Self::calculate_allocated_accounts_data_size(transaction); - debug!("transaction {:?} has cost {:?}", transaction, tx_cost); - TransactionCost::Transaction(tx_cost) + let usage_cost_details = UsageCostDetails { + transaction, + signature_cost, + write_lock_cost, + data_bytes_cost, + programs_execution_cost, + loaded_accounts_data_size_cost, + allocated_accounts_data_size, + signature_details: signatures_count_detail, + }; + + TransactionCost::Transaction(usage_cost_details) } } // Calculate executed transaction CU cost, with actual execution and loaded accounts size // costs. - pub fn calculate_cost_for_executed_transaction( - transaction: &SanitizedTransaction, + pub fn calculate_cost_for_executed_transaction<'a>( + transaction: &'a SanitizedTransaction, actual_programs_execution_cost: u64, actual_loaded_accounts_data_size_bytes: u32, feature_set: &FeatureSet, - ) -> TransactionCost { + ) -> TransactionCost<'a, SanitizedTransaction> { if transaction.is_simple_vote_transaction() { - TransactionCost::SimpleVote { - writable_accounts: Self::get_writable_accounts(transaction), - } + TransactionCost::SimpleVote { transaction } } else { - let mut tx_cost = UsageCostDetails::new_with_default_capacity(); + let (signatures_count_detail, signature_cost) = + Self::get_signature_cost(transaction, feature_set); + let write_lock_cost = Self::get_write_lock_cost(transaction, feature_set); - Self::get_signature_cost(&mut tx_cost, transaction, feature_set); - Self::get_write_lock_cost(&mut tx_cost, transaction, feature_set); - Self::get_instructions_data_cost(&mut tx_cost, transaction); - tx_cost.allocated_accounts_data_size = + let instructions_data_cost = Self::get_instructions_data_cost(transaction); + let allocated_accounts_data_size = Self::calculate_allocated_accounts_data_size(transaction); - tx_cost.programs_execution_cost = actual_programs_execution_cost; - tx_cost.loaded_accounts_data_size_cost = Self::calculate_loaded_accounts_data_size_cost( + let programs_execution_cost = actual_programs_execution_cost; + let loaded_accounts_data_size_cost = Self::calculate_loaded_accounts_data_size_cost( actual_loaded_accounts_data_size_bytes, feature_set, ); - TransactionCost::Transaction(tx_cost) + let usage_cost_details = UsageCostDetails { + transaction, + signature_cost, + write_lock_cost, + data_bytes_cost: instructions_data_cost, + programs_execution_cost, + loaded_accounts_data_size_cost, + allocated_accounts_data_size, + signature_details: signatures_count_detail, + }; + + TransactionCost::Transaction(usage_cost_details) } } + /// Returns signature details and the total signature cost fn get_signature_cost( - tx_cost: &mut UsageCostDetails, transaction: &SanitizedTransaction, feature_set: &FeatureSet, - ) { + ) -> (TransactionSignatureDetails, u64) { let signatures_count_detail = transaction.message().get_signature_details(); - tx_cost.num_transaction_signatures = signatures_count_detail.num_transaction_signatures(); - tx_cost.num_secp256k1_instruction_signatures = - signatures_count_detail.num_secp256k1_instruction_signatures(); - tx_cost.num_ed25519_instruction_signatures = - signatures_count_detail.num_ed25519_instruction_signatures(); let ed25519_verify_cost = if feature_set.is_active(&feature_set::ed25519_precompile_verify_strict::id()) { @@ -114,7 +126,7 @@ impl CostModel { ED25519_VERIFY_COST }; - tx_cost.signature_cost = signatures_count_detail + let signature_cost = signatures_count_detail .num_transaction_signatures() .saturating_mul(SIGNATURE_COST) .saturating_add( @@ -127,44 +139,34 @@ impl CostModel { .num_ed25519_instruction_signatures() .saturating_mul(ed25519_verify_cost), ); + + (signatures_count_detail, signature_cost) } - fn get_writable_accounts(transaction: &SanitizedTransaction) -> Vec { - let message = transaction.message(); + fn get_writable_accounts(message: &impl SVMMessage) -> impl Iterator { message .account_keys() .iter() .enumerate() - .filter_map(|(i, k)| { - if message.is_writable(i) { - Some(*k) - } else { - None - } - }) - .collect() + .filter_map(|(i, k)| message.is_writable(i).then_some(k)) } - fn get_write_lock_cost( - tx_cost: &mut UsageCostDetails, - transaction: &SanitizedTransaction, - feature_set: &FeatureSet, - ) { - tx_cost.writable_accounts = Self::get_writable_accounts(transaction); + /// Returns the total write-lock cost. + fn get_write_lock_cost(transaction: &impl SVMMessage, feature_set: &FeatureSet) -> u64 { let num_write_locks = if feature_set.is_active(&feature_set::cost_model_requested_write_lock_cost::id()) { - transaction.message().num_write_locks() + transaction.num_write_locks() } else { - tx_cost.writable_accounts.len() as u64 + Self::get_writable_accounts(transaction).count() as u64 }; - tx_cost.write_lock_cost = WRITE_LOCK_UNITS.saturating_mul(num_write_locks); + WRITE_LOCK_UNITS.saturating_mul(num_write_locks) } + /// Return (programs_execution_cost, loaded_accounts_data_size_cost, data_bytes_cost) fn get_transaction_cost( - tx_cost: &mut UsageCostDetails, transaction: &impl SVMMessage, feature_set: &FeatureSet, - ) { + ) -> (u64, u64, u64) { let mut programs_execution_costs = 0u64; let mut loaded_accounts_data_size_cost = 0u64; let mut data_bytes_len_total = 0u64; @@ -219,15 +221,15 @@ impl CostModel { } } - tx_cost.programs_execution_cost = programs_execution_costs; - tx_cost.loaded_accounts_data_size_cost = loaded_accounts_data_size_cost; - tx_cost.data_bytes_cost = data_bytes_len_total / INSTRUCTION_DATA_BYTES_COST; + ( + programs_execution_costs, + loaded_accounts_data_size_cost, + data_bytes_len_total / INSTRUCTION_DATA_BYTES_COST, + ) } - fn get_instructions_data_cost( - tx_cost: &mut UsageCostDetails, - transaction: &SanitizedTransaction, - ) { + /// Return the instruction data bytes cost. + fn get_instructions_data_cost(transaction: &SanitizedTransaction) -> u64 { let ix_data_bytes_len_total: u64 = transaction .message() .instructions() @@ -235,7 +237,7 @@ impl CostModel { .map(|instruction| instruction.data.len() as u64) .sum(); - tx_cost.data_bytes_cost = ix_data_bytes_len_total / INSTRUCTION_DATA_BYTES_COST; + ix_data_bytes_len_total / INSTRUCTION_DATA_BYTES_COST } pub fn calculate_loaded_accounts_data_size_cost( @@ -319,6 +321,8 @@ impl CostModel { mod tests { use { super::*, + itertools::Itertools, + log::debug, solana_sdk::{ compute_budget::{self, ComputeBudgetInstruction}, fee::ACCOUNT_DATA_COST_PAGE_SIZE, @@ -526,14 +530,11 @@ mod tests { .get(&system_program::id()) .unwrap(); - let mut tx_cost = UsageCostDetails::default(); - CostModel::get_transaction_cost( - &mut tx_cost, - &simple_transaction, - &FeatureSet::all_enabled(), - ); - assert_eq!(*expected_execution_cost, tx_cost.programs_execution_cost); - assert_eq!(3, tx_cost.data_bytes_cost); + let (program_execution_cost, _loaded_accounts_data_size_cost, data_bytes_cost) = + CostModel::get_transaction_cost(&simple_transaction, &FeatureSet::all_enabled()); + + assert_eq!(*expected_execution_cost, program_execution_cost); + assert_eq!(3, data_bytes_cost); } #[test] @@ -554,17 +555,13 @@ mod tests { let token_transaction = SanitizedTransaction::from_transaction_for_tests(tx); debug!("token_transaction {:?}", token_transaction); - let mut tx_cost = UsageCostDetails::default(); - CostModel::get_transaction_cost( - &mut tx_cost, - &token_transaction, - &FeatureSet::all_enabled(), - ); + let (program_execution_cost, _loaded_accounts_data_size_cost, data_bytes_cost) = + CostModel::get_transaction_cost(&token_transaction, &FeatureSet::all_enabled()); assert_eq!( DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64, - tx_cost.programs_execution_cost + program_execution_cost ); - assert_eq!(0, tx_cost.data_bytes_cost); + assert_eq!(0, data_bytes_cost); } #[test] @@ -581,7 +578,7 @@ mod tests { { let tx_cost = CostModel::calculate_cost(&simple_transaction, &FeatureSet::default()); assert_eq!(WRITE_LOCK_UNITS, tx_cost.write_lock_cost()); - assert_eq!(1, tx_cost.writable_accounts().len()); + assert_eq!(1, tx_cost.writable_accounts().count()); } // Feature enabled - write lock is demoted but still counts towards cost @@ -589,7 +586,7 @@ mod tests { let tx_cost = CostModel::calculate_cost(&simple_transaction, &FeatureSet::all_enabled()); assert_eq!(2 * WRITE_LOCK_UNITS, tx_cost.write_lock_cost()); - assert_eq!(1, tx_cost.writable_accounts().len()); + assert_eq!(1, tx_cost.writable_accounts().count()); } } @@ -619,15 +616,12 @@ mod tests { ); let token_transaction = SanitizedTransaction::from_transaction_for_tests(tx); - let mut tx_cost = UsageCostDetails::default(); - CostModel::get_transaction_cost( - &mut tx_cost, - &token_transaction, - &FeatureSet::all_enabled(), - ); + let (program_execution_cost, _loaded_accounts_data_size_cost, data_bytes_cost) = + CostModel::get_transaction_cost(&token_transaction, &FeatureSet::all_enabled()); + // If cu-limit is specified, that would the cost for all programs - assert_eq!(12_345, tx_cost.programs_execution_cost); - assert_eq!(1, tx_cost.data_bytes_cost); + assert_eq!(12_345, program_execution_cost); + assert_eq!(1, data_bytes_cost); } #[test] @@ -664,13 +658,9 @@ mod tests { ); let token_transaction = SanitizedTransaction::from_transaction_for_tests(tx); - let mut tx_cost = UsageCostDetails::default(); - CostModel::get_transaction_cost( - &mut tx_cost, - &token_transaction, - &FeatureSet::all_enabled(), - ); - assert_eq!(0, tx_cost.programs_execution_cost); + let (program_execution_cost, _loaded_accounts_data_size_cost, _data_bytes_cost) = + CostModel::get_transaction_cost(&token_transaction, &FeatureSet::all_enabled()); + assert_eq!(0, program_execution_cost); } #[test] @@ -695,10 +685,10 @@ mod tests { .unwrap(); let expected_cost = program_cost * 2; - let mut tx_cost = UsageCostDetails::default(); - CostModel::get_transaction_cost(&mut tx_cost, &tx, &FeatureSet::all_enabled()); - assert_eq!(expected_cost, tx_cost.programs_execution_cost); - assert_eq!(6, tx_cost.data_bytes_cost); + let (program_execution_cost, _loaded_accounts_data_size_cost, data_bytes_cost) = + CostModel::get_transaction_cost(&tx, &FeatureSet::all_enabled()); + assert_eq!(expected_cost, program_execution_cost); + assert_eq!(6, data_bytes_cost); } #[test] @@ -726,10 +716,10 @@ mod tests { debug!("many random transaction {:?}", tx); let expected_cost = DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64 * 2; - let mut tx_cost = UsageCostDetails::default(); - CostModel::get_transaction_cost(&mut tx_cost, &tx, &FeatureSet::all_enabled()); - assert_eq!(expected_cost, tx_cost.programs_execution_cost); - assert_eq!(0, tx_cost.data_bytes_cost); + let (program_execution_cost, _loaded_accounts_data_size_cost, data_bytes_cost) = + CostModel::get_transaction_cost(&tx, &FeatureSet::all_enabled()); + assert_eq!(expected_cost, program_execution_cost); + assert_eq!(0, data_bytes_cost); } #[test] @@ -756,11 +746,12 @@ mod tests { ); let tx_cost = CostModel::calculate_cost(&tx, &FeatureSet::all_enabled()); - assert_eq!(2 + 2, tx_cost.writable_accounts().len()); - assert_eq!(signer1.pubkey(), tx_cost.writable_accounts()[0]); - assert_eq!(signer2.pubkey(), tx_cost.writable_accounts()[1]); - assert_eq!(key1, tx_cost.writable_accounts()[2]); - assert_eq!(key2, tx_cost.writable_accounts()[3]); + let writable_accounts = tx_cost.writable_accounts().collect_vec(); + assert_eq!(2 + 2, writable_accounts.len()); + assert_eq!(signer1.pubkey(), *writable_accounts[0]); + assert_eq!(signer2.pubkey(), *writable_accounts[1]); + assert_eq!(key1, *writable_accounts[2]); + assert_eq!(key2, *writable_accounts[3]); } #[test] @@ -787,7 +778,7 @@ mod tests { let tx_cost = CostModel::calculate_cost(&tx, &FeatureSet::all_enabled()); assert_eq!(expected_account_cost, tx_cost.write_lock_cost()); assert_eq!(*expected_execution_cost, tx_cost.programs_execution_cost()); - assert_eq!(2, tx_cost.writable_accounts().len()); + assert_eq!(2, tx_cost.writable_accounts().count()); assert_eq!( expected_loaded_accounts_data_size_cost, tx_cost.loaded_accounts_data_size_cost() @@ -823,7 +814,7 @@ mod tests { let tx_cost = CostModel::calculate_cost(&tx, &feature_set); assert_eq!(expected_account_cost, tx_cost.write_lock_cost()); assert_eq!(expected_execution_cost, tx_cost.programs_execution_cost()); - assert_eq!(2, tx_cost.writable_accounts().len()); + assert_eq!(2, tx_cost.writable_accounts().count()); assert_eq!( expected_loaded_accounts_data_size_cost, tx_cost.loaded_accounts_data_size_cost() @@ -850,12 +841,12 @@ mod tests { .unwrap(); let expected_bpf_cost = DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT; - let mut tx_cost = UsageCostDetails::default(); - CostModel::get_transaction_cost(&mut tx_cost, &transaction, &FeatureSet::all_enabled()); + let (program_execution_cost, _loaded_accounts_data_size_cost, _data_bytes_cost) = + CostModel::get_transaction_cost(&transaction, &FeatureSet::all_enabled()); assert_eq!( expected_builtin_cost + expected_bpf_cost as u64, - tx_cost.programs_execution_cost + program_execution_cost ); } @@ -881,9 +872,8 @@ mod tests { .get(&compute_budget::id()) .unwrap(); - let mut tx_cost = UsageCostDetails::default(); - CostModel::get_transaction_cost(&mut tx_cost, &transaction, &FeatureSet::all_enabled()); - - assert_eq!(expected_cost, tx_cost.programs_execution_cost); + let (program_execution_cost, _loaded_accounts_data_size_cost, _data_bytes_cost) = + CostModel::get_transaction_cost(&transaction, &FeatureSet::all_enabled()); + assert_eq!(expected_cost, program_execution_cost); } } diff --git a/cost-model/src/cost_tracker.rs b/cost-model/src/cost_tracker.rs index 8caaa2ef3168cc..3b9382bba0a901 100644 --- a/cost-model/src/cost_tracker.rs +++ b/cost-model/src/cost_tracker.rs @@ -9,6 +9,7 @@ use { solana_sdk::{ clock::Slot, pubkey::Pubkey, saturating_add_assign, transaction::TransactionError, }, + solana_svm_transaction::svm_message::SVMMessage, std::{cmp::Ordering, collections::HashMap}, }; @@ -157,7 +158,10 @@ impl CostTracker { .saturating_sub(in_flight_transaction_count); } - pub fn try_add(&mut self, tx_cost: &TransactionCost) -> Result { + pub fn try_add( + &mut self, + tx_cost: &TransactionCost, + ) -> Result { self.would_fit(tx_cost)?; let updated_costliest_account_cost = self.add_transaction_cost(tx_cost); Ok(UpdatedCosts { @@ -168,7 +172,7 @@ impl CostTracker { pub fn update_execution_cost( &mut self, - estimated_tx_cost: &TransactionCost, + estimated_tx_cost: &TransactionCost, actual_execution_units: u64, actual_loaded_accounts_data_size_cost: u64, ) { @@ -194,7 +198,7 @@ impl CostTracker { } } - pub fn remove(&mut self, tx_cost: &TransactionCost) { + pub fn remove(&mut self, tx_cost: &TransactionCost) { self.remove_transaction_cost(tx_cost); } @@ -263,7 +267,10 @@ impl CostTracker { .unwrap_or_default() } - fn would_fit(&self, tx_cost: &TransactionCost) -> Result<(), CostTrackerError> { + fn would_fit( + &self, + tx_cost: &TransactionCost, + ) -> Result<(), CostTrackerError> { let cost: u64 = tx_cost.sum(); if tx_cost.is_simple_vote() { @@ -292,7 +299,7 @@ impl CostTracker { } // check each account against account_cost_limit, - for account_key in tx_cost.writable_accounts().iter() { + for account_key in tx_cost.writable_accounts() { match self.cost_by_writable_accounts.get(account_key) { Some(chained_cost) => { if chained_cost.saturating_add(cost) > self.account_cost_limit { @@ -309,7 +316,7 @@ impl CostTracker { } // Returns the highest account cost for all write-lock accounts `TransactionCost` updated - fn add_transaction_cost(&mut self, tx_cost: &TransactionCost) -> u64 { + fn add_transaction_cost(&mut self, tx_cost: &TransactionCost) -> u64 { saturating_add_assign!( self.allocated_accounts_data_size, tx_cost.allocated_accounts_data_size() @@ -330,7 +337,7 @@ impl CostTracker { self.add_transaction_execution_cost(tx_cost, tx_cost.sum()) } - fn remove_transaction_cost(&mut self, tx_cost: &TransactionCost) { + fn remove_transaction_cost(&mut self, tx_cost: &TransactionCost) { let cost = tx_cost.sum(); self.sub_transaction_execution_cost(tx_cost, cost); self.allocated_accounts_data_size = self @@ -352,11 +359,11 @@ impl CostTracker { /// Return the costliest account cost that were updated by `TransactionCost` fn add_transaction_execution_cost( &mut self, - tx_cost: &TransactionCost, + tx_cost: &TransactionCost, adjustment: u64, ) -> u64 { let mut costliest_account_cost = 0; - for account_key in tx_cost.writable_accounts().iter() { + for account_key in tx_cost.writable_accounts() { let account_cost = self .cost_by_writable_accounts .entry(*account_key) @@ -373,8 +380,12 @@ impl CostTracker { } /// Subtract extra execution units from cost_tracker - fn sub_transaction_execution_cost(&mut self, tx_cost: &TransactionCost, adjustment: u64) { - for account_key in tx_cost.writable_accounts().iter() { + fn sub_transaction_execution_cost( + &mut self, + tx_cost: &TransactionCost, + adjustment: u64, + ) { + for account_key in tx_cost.writable_accounts() { let account_cost = self .cost_by_writable_accounts .entry(*account_key) @@ -400,17 +411,11 @@ impl CostTracker { mod tests { use { super::*, - crate::transaction_cost::*, + crate::transaction_cost::{WritableKeysTransaction, *}, solana_sdk::{ - hash::Hash, - reserved_account_keys::ReservedAccountKeys, + message::TransactionSignatureDetails, signature::{Keypair, Signer}, - system_transaction, - transaction::{ - MessageHash, SanitizedTransaction, SimpleAddressLoader, VersionedTransaction, - }, }, - solana_vote_program::{vote_state::TowerSync, vote_transaction}, std::cmp, }; @@ -427,53 +432,45 @@ mod tests { } } - fn test_setup() -> (Keypair, Hash) { + fn test_setup() -> Keypair { solana_logger::setup(); - (Keypair::new(), Hash::new_unique()) + Keypair::new() } - fn build_simple_transaction( - mint_keypair: &Keypair, - start_hash: &Hash, - ) -> (SanitizedTransaction, TransactionCost) { - let keypair = Keypair::new(); - let simple_transaction = SanitizedTransaction::from_transaction_for_tests( - system_transaction::transfer(mint_keypair, &keypair.pubkey(), 2, *start_hash), - ); - let mut tx_cost = UsageCostDetails::new_with_capacity(1); - tx_cost.programs_execution_cost = 5; - tx_cost.writable_accounts.push(mint_keypair.pubkey()); - - (simple_transaction, TransactionCost::Transaction(tx_cost)) - } - - fn build_simple_vote_transaction( - mint_keypair: &Keypair, - start_hash: &Hash, - ) -> (SanitizedTransaction, TransactionCost) { - let keypair = Keypair::new(); - let transaction = vote_transaction::new_tower_sync_transaction( - TowerSync::from(vec![(42, 1)]), - *start_hash, - mint_keypair, - &keypair, - &keypair, - None, - ); - let vote_transaction = SanitizedTransaction::try_create( - VersionedTransaction::from(transaction), - MessageHash::Compute, - Some(true), - SimpleAddressLoader::Disabled, - &ReservedAccountKeys::empty_key_set(), - ) - .unwrap(); - - let writable_accounts = vec![mint_keypair.pubkey()]; - ( - vote_transaction, - TransactionCost::SimpleVote { writable_accounts }, - ) + fn build_simple_transaction(mint_keypair: &Keypair) -> WritableKeysTransaction { + WritableKeysTransaction(vec![mint_keypair.pubkey()]) + } + + fn simple_usage_cost_details( + transaction: &WritableKeysTransaction, + programs_execution_cost: u64, + ) -> UsageCostDetails { + UsageCostDetails { + transaction, + signature_cost: 0, + write_lock_cost: 0, + data_bytes_cost: 0, + programs_execution_cost, + loaded_accounts_data_size_cost: 0, + allocated_accounts_data_size: 0, + signature_details: TransactionSignatureDetails::new(0, 0, 0), + } + } + + fn simple_transaction_cost( + transaction: &WritableKeysTransaction, + programs_execution_cost: u64, + ) -> TransactionCost { + TransactionCost::Transaction(simple_usage_cost_details( + transaction, + programs_execution_cost, + )) + } + + fn simple_vote_transaction_cost( + transaction: &WritableKeysTransaction, + ) -> TransactionCost { + TransactionCost::SimpleVote { transaction } } #[test] @@ -488,8 +485,9 @@ mod tests { #[test] fn test_cost_tracker_ok_add_one() { - let (mint_keypair, start_hash) = test_setup(); - let (_tx, tx_cost) = build_simple_transaction(&mint_keypair, &start_hash); + let mint_keypair = test_setup(); + let tx = build_simple_transaction(&mint_keypair); + let tx_cost = simple_transaction_cost(&tx, 5); let cost = tx_cost.sum(); // build testee to have capacity for one simple transaction @@ -504,8 +502,9 @@ mod tests { #[test] fn test_cost_tracker_ok_add_one_vote() { - let (mint_keypair, start_hash) = test_setup(); - let (_tx, tx_cost) = build_simple_vote_transaction(&mint_keypair, &start_hash); + let mint_keypair = test_setup(); + let tx = build_simple_transaction(&mint_keypair); + let tx_cost = simple_vote_transaction_cost(&tx); let cost = tx_cost.sum(); // build testee to have capacity for one simple transaction @@ -520,8 +519,9 @@ mod tests { #[test] fn test_cost_tracker_add_data() { - let (mint_keypair, start_hash) = test_setup(); - let (_tx, mut tx_cost) = build_simple_transaction(&mint_keypair, &start_hash); + let mint_keypair = test_setup(); + let tx = build_simple_transaction(&mint_keypair); + let mut tx_cost = simple_transaction_cost(&tx, 5); if let TransactionCost::Transaction(ref mut usage_cost) = tx_cost { usage_cost.allocated_accounts_data_size = 1; } else { @@ -539,11 +539,13 @@ mod tests { #[test] fn test_cost_tracker_ok_add_two_same_accounts() { - let (mint_keypair, start_hash) = test_setup(); + let mint_keypair = test_setup(); // build two transactions with same signed account - let (_tx1, tx_cost1) = build_simple_transaction(&mint_keypair, &start_hash); + let tx1 = build_simple_transaction(&mint_keypair); + let tx_cost1 = simple_transaction_cost(&tx1, 5); let cost1 = tx_cost1.sum(); - let (_tx2, tx_cost2) = build_simple_transaction(&mint_keypair, &start_hash); + let tx2 = build_simple_transaction(&mint_keypair); + let tx_cost2 = simple_transaction_cost(&tx2, 5); let cost2 = tx_cost2.sum(); // build testee to have capacity for two simple transactions, with same accounts @@ -564,12 +566,15 @@ mod tests { #[test] fn test_cost_tracker_ok_add_two_diff_accounts() { - let (mint_keypair, start_hash) = test_setup(); + let mint_keypair = test_setup(); // build two transactions with diff accounts let second_account = Keypair::new(); - let (_tx1, tx_cost1) = build_simple_transaction(&mint_keypair, &start_hash); + let tx1 = build_simple_transaction(&mint_keypair); + let tx_cost1 = simple_transaction_cost(&tx1, 5); let cost1 = tx_cost1.sum(); - let (_tx2, tx_cost2) = build_simple_transaction(&second_account, &start_hash); + + let tx2 = build_simple_transaction(&second_account); + let tx_cost2 = simple_transaction_cost(&tx2, 5); let cost2 = tx_cost2.sum(); // build testee to have capacity for two simple transactions, with same accounts @@ -590,11 +595,13 @@ mod tests { #[test] fn test_cost_tracker_chain_reach_limit() { - let (mint_keypair, start_hash) = test_setup(); + let mint_keypair = test_setup(); // build two transactions with same signed account - let (_tx1, tx_cost1) = build_simple_transaction(&mint_keypair, &start_hash); + let tx1 = build_simple_transaction(&mint_keypair); + let tx_cost1 = simple_transaction_cost(&tx1, 5); let cost1 = tx_cost1.sum(); - let (_tx2, tx_cost2) = build_simple_transaction(&mint_keypair, &start_hash); + let tx2 = build_simple_transaction(&mint_keypair); + let tx_cost2 = simple_transaction_cost(&tx2, 5); let cost2 = tx_cost2.sum(); // build testee to have capacity for two simple transactions, but not for same accounts @@ -612,12 +619,14 @@ mod tests { #[test] fn test_cost_tracker_reach_limit() { - let (mint_keypair, start_hash) = test_setup(); + let mint_keypair = test_setup(); // build two transactions with diff accounts let second_account = Keypair::new(); - let (_tx1, tx_cost1) = build_simple_transaction(&mint_keypair, &start_hash); + let tx1 = build_simple_transaction(&mint_keypair); + let tx_cost1 = simple_transaction_cost(&tx1, 5); let cost1 = tx_cost1.sum(); - let (_tx2, tx_cost2) = build_simple_transaction(&second_account, &start_hash); + let tx2 = build_simple_transaction(&second_account); + let tx_cost2 = simple_transaction_cost(&tx2, 5); let cost2 = tx_cost2.sum(); // build testee to have capacity for each chain, but not enough room for both transactions @@ -636,12 +645,14 @@ mod tests { #[test] fn test_cost_tracker_reach_vote_limit() { - let (mint_keypair, start_hash) = test_setup(); + let mint_keypair = test_setup(); // build two mocking vote transactions with diff accounts let second_account = Keypair::new(); - let (_tx1, tx_cost1) = build_simple_vote_transaction(&mint_keypair, &start_hash); - let (_tx2, tx_cost2) = build_simple_vote_transaction(&second_account, &start_hash); + let tx1 = build_simple_transaction(&mint_keypair); + let tx_cost1 = simple_vote_transaction_cost(&tx1); let cost1 = tx_cost1.sum(); + let tx2 = build_simple_transaction(&second_account); + let tx_cost2 = simple_vote_transaction_cost(&tx2); let cost2 = tx_cost2.sum(); // build testee to have capacity for each chain, but not enough room for both votes @@ -658,18 +669,21 @@ mod tests { // however there is room for none-vote tx3 { let third_account = Keypair::new(); - let (_tx3, tx_cost3) = build_simple_transaction(&third_account, &start_hash); + let tx3 = build_simple_transaction(&third_account); + let tx_cost3 = simple_transaction_cost(&tx3, 5); assert!(testee.would_fit(&tx_cost3).is_ok()); } } #[test] fn test_cost_tracker_reach_data_block_limit() { - let (mint_keypair, start_hash) = test_setup(); + let mint_keypair = test_setup(); // build two transactions with diff accounts let second_account = Keypair::new(); - let (_tx1, mut tx_cost1) = build_simple_transaction(&mint_keypair, &start_hash); - let (_tx2, mut tx_cost2) = build_simple_transaction(&second_account, &start_hash); + let tx1 = build_simple_transaction(&mint_keypair); + let mut tx_cost1 = simple_transaction_cost(&tx1, 5); + let tx2 = build_simple_transaction(&second_account); + let mut tx_cost2 = simple_transaction_cost(&tx2, 5); if let TransactionCost::Transaction(ref mut usage_cost) = tx_cost1 { usage_cost.allocated_accounts_data_size = MAX_BLOCK_ACCOUNTS_DATA_SIZE_DELTA; } else { @@ -695,13 +709,16 @@ mod tests { #[test] fn test_cost_tracker_remove() { - let (mint_keypair, start_hash) = test_setup(); + let mint_keypair = test_setup(); // build two transactions with diff accounts let second_account = Keypair::new(); - let (_tx1, tx_cost1) = build_simple_transaction(&mint_keypair, &start_hash); - let (_tx2, tx_cost2) = build_simple_transaction(&second_account, &start_hash); + let tx1 = build_simple_transaction(&mint_keypair); + let tx_cost1 = simple_transaction_cost(&tx1, 5); + let tx2 = build_simple_transaction(&second_account); + let tx_cost2 = simple_transaction_cost(&tx2, 5); let cost1 = tx_cost1.sum(); let cost2 = tx_cost2.sum(); + // build testee let mut testee = CostTracker::new(cost1 + cost2, cost1 + cost2, cost1 + cost2); @@ -738,11 +755,8 @@ mod tests { // | acct3 | $cost | // and block_cost = $cost { - let tx_cost = TransactionCost::Transaction(UsageCostDetails { - writable_accounts: vec![acct1, acct2, acct3], - programs_execution_cost: cost, - ..UsageCostDetails::default() - }); + let transaction = WritableKeysTransaction(vec![acct1, acct2, acct3]); + let tx_cost = simple_transaction_cost(&transaction, cost); assert!(testee.try_add(&tx_cost).is_ok()); let (_costliest_account, costliest_account_cost) = testee.find_costliest_account(); assert_eq!(cost, testee.block_cost); @@ -756,11 +770,8 @@ mod tests { // | acct3 | $cost | // and block_cost = $cost * 2 { - let tx_cost = TransactionCost::Transaction(UsageCostDetails { - writable_accounts: vec![acct2], - programs_execution_cost: cost, - ..UsageCostDetails::default() - }); + let transaction = WritableKeysTransaction(vec![acct2]); + let tx_cost = simple_transaction_cost(&transaction, cost); assert!(testee.try_add(&tx_cost).is_ok()); let (costliest_account, costliest_account_cost) = testee.find_costliest_account(); assert_eq!(cost * 2, testee.block_cost); @@ -776,11 +787,8 @@ mod tests { // | acct3 | $cost | // and block_cost = $cost * 2 { - let tx_cost = TransactionCost::Transaction(UsageCostDetails { - writable_accounts: vec![acct1, acct2], - programs_execution_cost: cost, - ..UsageCostDetails::default() - }); + let transaction = WritableKeysTransaction(vec![acct1, acct2]); + let tx_cost = simple_transaction_cost(&transaction, cost); assert!(testee.try_add(&tx_cost).is_err()); let (costliest_account, costliest_account_cost) = testee.find_costliest_account(); assert_eq!(cost * 2, testee.block_cost); @@ -800,11 +808,8 @@ mod tests { let block_max = account_max * 3; // for three accts let mut testee = CostTracker::new(account_max, block_max, block_max); - let tx_cost = TransactionCost::Transaction(UsageCostDetails { - writable_accounts: vec![acct1, acct2, acct3], - programs_execution_cost: cost, - ..UsageCostDetails::default() - }); + let transaction = WritableKeysTransaction(vec![acct1, acct2, acct3]); + let tx_cost = simple_transaction_cost(&transaction, cost); let mut expected_block_cost = tx_cost.sum(); let expected_tx_count = 1; assert!(testee.try_add(&tx_cost).is_ok()); @@ -885,16 +890,16 @@ mod tests { let estimated_programs_execution_cost = 100; let estimated_loaded_accounts_data_size_cost = 200; let number_writeble_accounts = 3; - let writable_accounts = std::iter::repeat_with(Pubkey::new_unique) - .take(number_writeble_accounts) - .collect(); - - let tx_cost = TransactionCost::Transaction(UsageCostDetails { - writable_accounts, - programs_execution_cost: estimated_programs_execution_cost, - loaded_accounts_data_size_cost: estimated_loaded_accounts_data_size_cost, - ..UsageCostDetails::default() - }); + let transaction = WritableKeysTransaction( + std::iter::repeat_with(Pubkey::new_unique) + .take(number_writeble_accounts) + .collect(), + ); + + let mut usage_cost = + simple_usage_cost_details(&transaction, estimated_programs_execution_cost); + usage_cost.loaded_accounts_data_size_cost = estimated_loaded_accounts_data_size_cost; + let tx_cost = TransactionCost::Transaction(usage_cost); // confirm tx_cost is only made up by programs_execution_cost and // loaded_accounts_data_size_cost let estimated_tx_cost = tx_cost.sum(); @@ -952,12 +957,8 @@ mod tests { let mut cost_tracker = CostTracker::default(); let cost = 100u64; - let tx_cost = TransactionCost::Transaction(UsageCostDetails { - writable_accounts: vec![Pubkey::new_unique()], - programs_execution_cost: cost, - ..UsageCostDetails::default() - }); - + let transaction = WritableKeysTransaction(vec![Pubkey::new_unique()]); + let tx_cost = simple_transaction_cost(&transaction, cost); cost_tracker.add_transaction_cost(&tx_cost); // assert cost_tracker is reverted to default assert_eq!(1, cost_tracker.transaction_count); diff --git a/cost-model/src/transaction_cost.rs b/cost-model/src/transaction_cost.rs index 3065162c5ee22b..fcfdfdda195aa1 100644 --- a/cost-model/src/transaction_cost.rs +++ b/cost-model/src/transaction_cost.rs @@ -1,4 +1,8 @@ -use {crate::block_cost_limits, solana_sdk::pubkey::Pubkey}; +use { + crate::block_cost_limits, + solana_sdk::{message::TransactionSignatureDetails, pubkey::Pubkey}, + solana_svm_transaction::svm_message::SVMMessage, +}; /// TransactionCost is used to represent resources required to process /// a transaction, denominated in CU (eg. Compute Units). @@ -11,12 +15,12 @@ use {crate::block_cost_limits, solana_sdk::pubkey::Pubkey}; const SIMPLE_VOTE_USAGE_COST: u64 = 3428; #[derive(Debug)] -pub enum TransactionCost { - SimpleVote { writable_accounts: Vec }, - Transaction(UsageCostDetails), +pub enum TransactionCost<'a, Tx: SVMMessage> { + SimpleVote { transaction: &'a Tx }, + Transaction(UsageCostDetails<'a, Tx>), } -impl TransactionCost { +impl<'a, Tx: SVMMessage> TransactionCost<'a, Tx> { pub fn sum(&self) -> u64 { #![allow(clippy::assertions_on_constants)] match self { @@ -85,112 +89,133 @@ impl TransactionCost { } } - pub fn writable_accounts(&self) -> &[Pubkey] { - match self { - Self::SimpleVote { writable_accounts } => writable_accounts, - Self::Transaction(usage_cost) => &usage_cost.writable_accounts, - } + pub fn writable_accounts(&self) -> impl Iterator { + let transaction = match self { + Self::SimpleVote { transaction } => transaction, + Self::Transaction(usage_cost) => usage_cost.transaction, + }; + transaction + .account_keys() + .iter() + .enumerate() + .filter_map(|(index, key)| transaction.is_writable(index).then_some(key)) } pub fn num_transaction_signatures(&self) -> u64 { match self { Self::SimpleVote { .. } => 1, - Self::Transaction(usage_cost) => usage_cost.num_transaction_signatures, + Self::Transaction(usage_cost) => { + usage_cost.signature_details.num_transaction_signatures() + } } } pub fn num_secp256k1_instruction_signatures(&self) -> u64 { match self { Self::SimpleVote { .. } => 0, - Self::Transaction(usage_cost) => usage_cost.num_secp256k1_instruction_signatures, + Self::Transaction(usage_cost) => usage_cost + .signature_details + .num_secp256k1_instruction_signatures(), } } pub fn num_ed25519_instruction_signatures(&self) -> u64 { match self { Self::SimpleVote { .. } => 0, - Self::Transaction(usage_cost) => usage_cost.num_ed25519_instruction_signatures, + Self::Transaction(usage_cost) => usage_cost + .signature_details + .num_ed25519_instruction_signatures(), } } } -const MAX_WRITABLE_ACCOUNTS: usize = 256; - // costs are stored in number of 'compute unit's #[derive(Debug)] -pub struct UsageCostDetails { - pub writable_accounts: Vec, +pub struct UsageCostDetails<'a, Tx: SVMMessage> { + pub transaction: &'a Tx, pub signature_cost: u64, pub write_lock_cost: u64, pub data_bytes_cost: u64, pub programs_execution_cost: u64, pub loaded_accounts_data_size_cost: u64, pub allocated_accounts_data_size: u64, - pub num_transaction_signatures: u64, - pub num_secp256k1_instruction_signatures: u64, - pub num_ed25519_instruction_signatures: u64, + pub signature_details: TransactionSignatureDetails, } -impl Default for UsageCostDetails { - fn default() -> Self { - Self { - writable_accounts: Vec::with_capacity(MAX_WRITABLE_ACCOUNTS), - signature_cost: 0u64, - write_lock_cost: 0u64, - data_bytes_cost: 0u64, - programs_execution_cost: 0u64, - loaded_accounts_data_size_cost: 0u64, - allocated_accounts_data_size: 0u64, - num_transaction_signatures: 0u64, - num_secp256k1_instruction_signatures: 0u64, - num_ed25519_instruction_signatures: 0u64, - } +impl<'a, Tx: SVMMessage> UsageCostDetails<'a, Tx> { + pub fn sum(&self) -> u64 { + self.signature_cost + .saturating_add(self.write_lock_cost) + .saturating_add(self.data_bytes_cost) + .saturating_add(self.programs_execution_cost) + .saturating_add(self.loaded_accounts_data_size_cost) } } -#[cfg(test)] -impl PartialEq for UsageCostDetails { - fn eq(&self, other: &Self) -> bool { - fn to_hash_set(v: &[Pubkey]) -> std::collections::HashSet<&Pubkey> { - v.iter().collect() - } +#[cfg(feature = "dev-context-only-utils")] +#[derive(Debug)] +pub struct WritableKeysTransaction(pub Vec); - self.signature_cost == other.signature_cost - && self.write_lock_cost == other.write_lock_cost - && self.data_bytes_cost == other.data_bytes_cost - && self.programs_execution_cost == other.programs_execution_cost - && self.loaded_accounts_data_size_cost == other.loaded_accounts_data_size_cost - && self.allocated_accounts_data_size == other.allocated_accounts_data_size - && self.num_transaction_signatures == other.num_transaction_signatures - && self.num_secp256k1_instruction_signatures - == other.num_secp256k1_instruction_signatures - && self.num_ed25519_instruction_signatures == other.num_ed25519_instruction_signatures - && to_hash_set(&self.writable_accounts) == to_hash_set(&other.writable_accounts) +#[cfg(feature = "dev-context-only-utils")] +impl SVMMessage for WritableKeysTransaction { + fn num_total_signatures(&self) -> u64 { + unimplemented!("WritableKeysTransaction::num_total_signatures") } -} -#[cfg(test)] -impl Eq for UsageCostDetails {} - -impl UsageCostDetails { - #[cfg(test)] - pub fn new_with_capacity(capacity: usize) -> Self { - Self { - writable_accounts: Vec::with_capacity(capacity), - ..Self::default() - } + fn num_write_locks(&self) -> u64 { + unimplemented!("WritableKeysTransaction::num_write_locks") } - pub fn new_with_default_capacity() -> Self { - Self::default() + fn recent_blockhash(&self) -> &solana_sdk::hash::Hash { + unimplemented!("WritableKeysTransaction::recent_blockhash") } - pub fn sum(&self) -> u64 { - self.signature_cost - .saturating_add(self.write_lock_cost) - .saturating_add(self.data_bytes_cost) - .saturating_add(self.programs_execution_cost) - .saturating_add(self.loaded_accounts_data_size_cost) + fn num_instructions(&self) -> usize { + unimplemented!("WritableKeysTransaction::num_instructions") + } + + fn instructions_iter( + &self, + ) -> impl Iterator { + core::iter::empty() + } + + fn program_instructions_iter( + &self, + ) -> impl Iterator { + core::iter::empty() + } + + fn account_keys(&self) -> solana_sdk::message::AccountKeys { + solana_sdk::message::AccountKeys::new(&self.0, None) + } + + fn fee_payer(&self) -> &Pubkey { + unimplemented!("WritableKeysTransaction::fee_payer") + } + + fn is_writable(&self, _index: usize) -> bool { + true + } + + fn is_signer(&self, _index: usize) -> bool { + unimplemented!("WritableKeysTransaction::is_signer") + } + + fn is_invoked(&self, _key_index: usize) -> bool { + unimplemented!("WritableKeysTransaction::is_invoked") + } + + fn num_lookup_tables(&self) -> usize { + unimplemented!("WritableKeysTransaction::num_lookup_tables") + } + + fn message_address_table_lookups( + &self, + ) -> impl Iterator< + Item = solana_svm_transaction::message_address_table_lookup::SVMMessageAddressTableLookup, + > { + core::iter::empty() } }