From 4c418143b603582eb57d0b79712c42a8fbf6ced6 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 12 Mar 2024 20:47:16 -0500 Subject: [PATCH] v1.17: add precompile signature metrics to cost tracker (backport of #133) (#142) * add precompile signature metrics to cost tracker (#133) (cherry picked from commit 9770cd9083126b4dfe40fb207b0a3b8b21f33d21) # Conflicts: # cost-model/src/cost_model.rs # cost-model/src/cost_tracker.rs * merge fix * fmt --------- Co-authored-by: Tao Zhu <82401714+tao-stones@users.noreply.github.com> Co-authored-by: Tao Zhu --- cost-model/src/block_cost_limits.rs | 8 +- cost-model/src/cost_model.rs | 24 +++++- cost-model/src/cost_tracker.rs | 42 ++++++++++ cost-model/src/transaction_cost.rs | 31 ++++++++ sdk/program/src/message/sanitized.rs | 112 ++++++++++++++++++++++++--- 5 files changed, 202 insertions(+), 15 deletions(-) diff --git a/cost-model/src/block_cost_limits.rs b/cost-model/src/block_cost_limits.rs index 328d89cd04198b..b04f289e0553af 100644 --- a/cost-model/src/block_cost_limits.rs +++ b/cost-model/src/block_cost_limits.rs @@ -24,6 +24,10 @@ pub const MAX_CONCURRENCY: u64 = 4; pub const COMPUTE_UNIT_TO_US_RATIO: u64 = 30; /// Number of compute units for one signature verification. pub const SIGNATURE_COST: u64 = COMPUTE_UNIT_TO_US_RATIO * 24; +/// Number of compute units for one secp256k1 signature verification. +pub const SECP256K1_VERIFY_COST: u64 = COMPUTE_UNIT_TO_US_RATIO * 223; +/// Number of compute units for one ed25519 signature verification. +pub const ED25519_VERIFY_COST: u64 = COMPUTE_UNIT_TO_US_RATIO * 76; /// Number of compute units for one write lock pub const WRITE_LOCK_UNITS: u64 = COMPUTE_UNIT_TO_US_RATIO * 10; /// Number of data bytes per compute units @@ -43,8 +47,8 @@ lazy_static! { (bpf_loader::id(), solana_bpf_loader_program::DEFAULT_LOADER_COMPUTE_UNITS), (loader_v4::id(), solana_loader_v4_program::DEFAULT_COMPUTE_UNITS), // Note: These are precompile, run directly in bank during sanitizing; - (secp256k1_program::id(), COMPUTE_UNIT_TO_US_RATIO * 24), - (ed25519_program::id(), COMPUTE_UNIT_TO_US_RATIO * 24), + (secp256k1_program::id(), 0), + (ed25519_program::id(), 0), ] .iter() .cloned() diff --git a/cost-model/src/cost_model.rs b/cost-model/src/cost_model.rs index 0e8d6954202351..d44d6550dc67de 100644 --- a/cost-model/src/cost_model.rs +++ b/cost-model/src/cost_model.rs @@ -43,7 +43,7 @@ impl CostModel { } else { let mut tx_cost = UsageCostDetails::new_with_default_capacity(); - tx_cost.signature_cost = Self::get_signature_cost(transaction); + Self::get_signature_cost(&mut tx_cost, transaction); Self::get_write_lock_cost(&mut tx_cost, transaction); Self::get_transaction_cost(&mut tx_cost, transaction, feature_set); tx_cost.account_data_size = Self::calculate_account_data_size(transaction); @@ -69,8 +69,26 @@ impl CostModel { ) } - fn get_signature_cost(transaction: &SanitizedTransaction) -> u64 { - transaction.signatures().len() as u64 * SIGNATURE_COST + fn get_signature_cost(tx_cost: &mut UsageCostDetails, transaction: &SanitizedTransaction) { + 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(); + tx_cost.signature_cost = signatures_count_detail + .num_transaction_signatures() + .saturating_mul(SIGNATURE_COST) + .saturating_add( + signatures_count_detail + .num_secp256k1_instruction_signatures() + .saturating_mul(SECP256K1_VERIFY_COST), + ) + .saturating_add( + signatures_count_detail + .num_ed25519_instruction_signatures() + .saturating_mul(ED25519_VERIFY_COST), + ); } fn get_writable_accounts(transaction: &SanitizedTransaction) -> Vec { diff --git a/cost-model/src/cost_tracker.rs b/cost-model/src/cost_tracker.rs index 0841849bf1d90c..33b0172eacd466 100644 --- a/cost-model/src/cost_tracker.rs +++ b/cost-model/src/cost_tracker.rs @@ -62,6 +62,9 @@ pub struct CostTracker { /// The amount of total account data size remaining. If `Some`, then do not add transactions /// that would cause `account_data_size` to exceed this limit. account_data_size_limit: Option, + transaction_signature_count: u64, + secp256k1_instruction_signature_count: u64, + ed25519_instruction_signature_count: u64, } impl Default for CostTracker { @@ -82,6 +85,9 @@ impl Default for CostTracker { transaction_count: 0, account_data_size: 0, account_data_size_limit: None, + transaction_signature_count: 0, + secp256k1_instruction_signature_count: 0, + ed25519_instruction_signature_count: 0, } } } @@ -167,6 +173,21 @@ impl CostTracker { ("costliest_account", costliest_account.to_string(), String), ("costliest_account_cost", costliest_account_cost as i64, i64), ("account_data_size", self.account_data_size, i64), + ( + "transaction_signature_count", + self.transaction_signature_count, + i64 + ), + ( + "secp256k1_instruction_signature_count", + self.secp256k1_instruction_signature_count, + i64 + ), + ( + "ed25519_instruction_signature_count", + self.ed25519_instruction_signature_count, + i64 + ), ); } @@ -234,6 +255,18 @@ impl CostTracker { self.add_transaction_execution_cost(tx_cost, tx_cost.sum()); saturating_add_assign!(self.account_data_size, tx_cost.account_data_size()); saturating_add_assign!(self.transaction_count, 1); + saturating_add_assign!( + self.transaction_signature_count, + tx_cost.num_transaction_signatures() + ); + saturating_add_assign!( + self.secp256k1_instruction_signature_count, + tx_cost.num_secp256k1_instruction_signatures() + ); + saturating_add_assign!( + self.ed25519_instruction_signature_count, + tx_cost.num_ed25519_instruction_signatures() + ); } fn remove_transaction_cost(&mut self, tx_cost: &TransactionCost) { @@ -243,6 +276,15 @@ impl CostTracker { .account_data_size .saturating_sub(tx_cost.account_data_size()); self.transaction_count = self.transaction_count.saturating_sub(1); + self.transaction_signature_count = self + .transaction_signature_count + .saturating_sub(tx_cost.num_transaction_signatures()); + self.secp256k1_instruction_signature_count = self + .secp256k1_instruction_signature_count + .saturating_sub(tx_cost.num_secp256k1_instruction_signatures()); + self.ed25519_instruction_signature_count = self + .ed25519_instruction_signature_count + .saturating_sub(tx_cost.num_ed25519_instruction_signatures()); } /// Apply additional actual execution units to cost_tracker diff --git a/cost-model/src/transaction_cost.rs b/cost-model/src/transaction_cost.rs index e765eee3bc7038..170be03e037d50 100644 --- a/cost-model/src/transaction_cost.rs +++ b/cost-model/src/transaction_cost.rs @@ -87,6 +87,27 @@ impl TransactionCost { Self::Transaction(usage_cost) => &usage_cost.writable_accounts, } } + + pub fn num_transaction_signatures(&self) -> u64 { + match self { + Self::SimpleVote { .. } => 1, + Self::Transaction(usage_cost) => usage_cost.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, + } + } + + pub fn num_ed25519_instruction_signatures(&self) -> u64 { + match self { + Self::SimpleVote { .. } => 0, + Self::Transaction(usage_cost) => usage_cost.num_ed25519_instruction_signatures, + } + } } const MAX_WRITABLE_ACCOUNTS: usize = 256; @@ -102,6 +123,9 @@ pub struct UsageCostDetails { pub bpf_execution_cost: u64, pub loaded_accounts_data_size_cost: u64, pub account_data_size: u64, + pub num_transaction_signatures: u64, + pub num_secp256k1_instruction_signatures: u64, + pub num_ed25519_instruction_signatures: u64, } impl Default for UsageCostDetails { @@ -115,6 +139,9 @@ impl Default for UsageCostDetails { bpf_execution_cost: 0u64, loaded_accounts_data_size_cost: 0u64, account_data_size: 0u64, + num_transaction_signatures: 0u64, + num_secp256k1_instruction_signatures: 0u64, + num_ed25519_instruction_signatures: 0u64, } } } @@ -133,6 +160,10 @@ impl PartialEq for UsageCostDetails { && self.bpf_execution_cost == other.bpf_execution_cost && self.loaded_accounts_data_size_cost == other.loaded_accounts_data_size_cost && self.account_data_size == other.account_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) } } diff --git a/sdk/program/src/message/sanitized.rs b/sdk/program/src/message/sanitized.rs index 640159a7ad2dea..066f4505e40589 100644 --- a/sdk/program/src/message/sanitized.rs +++ b/sdk/program/src/message/sanitized.rs @@ -347,23 +347,75 @@ impl SanitizedMessage { } pub fn num_signatures(&self) -> u64 { - let mut num_signatures = u64::from(self.header().num_required_signatures); - // This next part is really calculating the number of pre-processor - // operations being done and treating them like a signature + self.get_signature_details().total_signatures() + } + + pub fn num_write_locks(&self) -> u64 { + self.account_keys() + .len() + .saturating_sub(self.num_readonly_accounts()) as u64 + } + + /// return detailed signature counts + pub fn get_signature_details(&self) -> TransactionSignatureDetails { + let mut transaction_signature_details = TransactionSignatureDetails { + num_transaction_signatures: u64::from(self.header().num_required_signatures), + ..TransactionSignatureDetails::default() + }; + + // counting the number of pre-processor operations separately for (program_id, instruction) in self.program_instructions_iter() { - if secp256k1_program::check_id(program_id) || ed25519_program::check_id(program_id) { + if secp256k1_program::check_id(program_id) { if let Some(num_verifies) = instruction.data.first() { - num_signatures = num_signatures.saturating_add(u64::from(*num_verifies)); + transaction_signature_details.num_secp256k1_instruction_signatures = + transaction_signature_details + .num_secp256k1_instruction_signatures + .saturating_add(u64::from(*num_verifies)); + } + } else if ed25519_program::check_id(program_id) { + if let Some(num_verifies) = instruction.data.first() { + transaction_signature_details.num_ed25519_instruction_signatures = + transaction_signature_details + .num_ed25519_instruction_signatures + .saturating_add(u64::from(*num_verifies)); } } } - num_signatures + + transaction_signature_details } +} - pub fn num_write_locks(&self) -> u64 { - self.account_keys() - .len() - .saturating_sub(self.num_readonly_accounts()) as u64 +#[derive(Default)] +/// Transaction signature details including the number of transaction signatures +/// and precompile signatures. +pub struct TransactionSignatureDetails { + num_transaction_signatures: u64, + num_secp256k1_instruction_signatures: u64, + num_ed25519_instruction_signatures: u64, +} + +impl TransactionSignatureDetails { + /// return total number of signature, treating pre-processor operations as signature + pub(crate) fn total_signatures(&self) -> u64 { + self.num_transaction_signatures + .saturating_add(self.num_secp256k1_instruction_signatures) + .saturating_add(self.num_ed25519_instruction_signatures) + } + + /// return the number of transaction signatures + pub fn num_transaction_signatures(&self) -> u64 { + self.num_transaction_signatures + } + + /// return the number of secp256k1 instruction signatures + pub fn num_secp256k1_instruction_signatures(&self) -> u64 { + self.num_secp256k1_instruction_signatures + } + + /// return the number of ed25519 instruction signatures + pub fn num_ed25519_instruction_signatures(&self) -> u64 { + self.num_ed25519_instruction_signatures } } @@ -559,4 +611,44 @@ mod tests { } } } + + #[test] + fn test_get_signature_details() { + let key0 = Pubkey::new_unique(); + let key1 = Pubkey::new_unique(); + let loader_key = Pubkey::new_unique(); + + let loader_instr = CompiledInstruction::new(2, &(), vec![0, 1]); + let mock_secp256k1_instr = CompiledInstruction::new(3, &[1u8; 10], vec![]); + let mock_ed25519_instr = CompiledInstruction::new(4, &[5u8; 10], vec![]); + + let message = SanitizedMessage::try_from(legacy::Message::new_with_compiled_instructions( + 2, + 1, + 2, + vec![ + key0, + key1, + loader_key, + secp256k1_program::id(), + ed25519_program::id(), + ], + Hash::default(), + vec![ + loader_instr, + mock_secp256k1_instr.clone(), + mock_ed25519_instr, + mock_secp256k1_instr, + ], + )) + .unwrap(); + + let signature_details = message.get_signature_details(); + // expect 2 required transaction signatures + assert_eq!(2, signature_details.num_transaction_signatures); + // expect 2 secp256k1 instruction signatures - 1 for each mock_secp2561k1_instr + assert_eq!(2, signature_details.num_secp256k1_instruction_signatures); + // expect 5 ed25519 instruction signatures from mock_ed25519_instr + assert_eq!(5, signature_details.num_ed25519_instruction_signatures); + } }