1#[cfg(feature = "dev-context-only-utils")]
2use solana_compute_budget_instruction::compute_budget_instruction_details::ComputeBudgetInstructionDetails;
3use {
4 crate::block_cost_limits, solana_pubkey::Pubkey,
5 solana_runtime_transaction::transaction_meta::StaticMeta,
6 solana_svm_transaction::svm_message::SVMMessage,
7};
8
9const SIMPLE_VOTE_USAGE_COST: u64 = 3428;
18
19#[derive(Debug)]
20pub enum TransactionCost<'a, Tx> {
21 SimpleVote { transaction: &'a Tx },
22 Transaction(UsageCostDetails<'a, Tx>),
23}
24
25impl<Tx> TransactionCost<'_, Tx> {
26 pub fn sum(&self) -> u64 {
27 #![allow(clippy::assertions_on_constants)]
28 match self {
29 Self::SimpleVote { .. } => {
30 const _: () = assert!(
31 SIMPLE_VOTE_USAGE_COST
32 == solana_vote_program::vote_processor::DEFAULT_COMPUTE_UNITS
33 + block_cost_limits::SIGNATURE_COST
34 + 2 * block_cost_limits::WRITE_LOCK_UNITS
35 + 8
36 );
37
38 SIMPLE_VOTE_USAGE_COST
39 }
40 Self::Transaction(usage_cost) => usage_cost.sum(),
41 }
42 }
43
44 pub fn programs_execution_cost(&self) -> u64 {
45 match self {
46 Self::SimpleVote { .. } => solana_vote_program::vote_processor::DEFAULT_COMPUTE_UNITS,
47 Self::Transaction(usage_cost) => usage_cost.programs_execution_cost,
48 }
49 }
50
51 pub fn is_simple_vote(&self) -> bool {
52 match self {
53 Self::SimpleVote { .. } => true,
54 Self::Transaction(_) => false,
55 }
56 }
57
58 pub fn data_bytes_cost(&self) -> u64 {
59 match self {
60 Self::SimpleVote { .. } => 0,
61 Self::Transaction(usage_cost) => usage_cost.data_bytes_cost,
62 }
63 }
64
65 pub fn allocated_accounts_data_size(&self) -> u64 {
66 match self {
67 Self::SimpleVote { .. } => 0,
68 Self::Transaction(usage_cost) => usage_cost.allocated_accounts_data_size,
69 }
70 }
71
72 pub fn loaded_accounts_data_size_cost(&self) -> u64 {
73 match self {
74 Self::SimpleVote { .. } => 8, Self::Transaction(usage_cost) => usage_cost.loaded_accounts_data_size_cost,
77 }
78 }
79
80 pub fn signature_cost(&self) -> u64 {
81 match self {
82 Self::SimpleVote { .. } => block_cost_limits::SIGNATURE_COST,
83 Self::Transaction(usage_cost) => usage_cost.signature_cost,
84 }
85 }
86
87 pub fn write_lock_cost(&self) -> u64 {
88 match self {
89 Self::SimpleVote { .. } => block_cost_limits::WRITE_LOCK_UNITS.saturating_mul(2),
90 Self::Transaction(usage_cost) => usage_cost.write_lock_cost,
91 }
92 }
93}
94
95impl<Tx: SVMMessage> TransactionCost<'_, Tx> {
96 pub fn writable_accounts(&self) -> impl Iterator<Item = &Pubkey> {
97 let transaction = match self {
98 Self::SimpleVote { transaction } => transaction,
99 Self::Transaction(usage_cost) => usage_cost.transaction,
100 };
101 transaction
102 .account_keys()
103 .iter()
104 .enumerate()
105 .filter_map(|(index, key)| transaction.is_writable(index).then_some(key))
106 }
107}
108
109impl<Tx: StaticMeta> TransactionCost<'_, Tx> {
110 pub fn num_transaction_signatures(&self) -> u64 {
111 match self {
112 Self::SimpleVote { .. } => 1,
113 Self::Transaction(usage_cost) => usage_cost
114 .transaction
115 .signature_details()
116 .num_transaction_signatures(),
117 }
118 }
119
120 pub fn num_secp256k1_instruction_signatures(&self) -> u64 {
121 match self {
122 Self::SimpleVote { .. } => 0,
123 Self::Transaction(usage_cost) => usage_cost
124 .transaction
125 .signature_details()
126 .num_secp256k1_instruction_signatures(),
127 }
128 }
129
130 pub fn num_ed25519_instruction_signatures(&self) -> u64 {
131 match self {
132 Self::SimpleVote { .. } => 0,
133 Self::Transaction(usage_cost) => usage_cost
134 .transaction
135 .signature_details()
136 .num_ed25519_instruction_signatures(),
137 }
138 }
139
140 pub fn num_secp256r1_instruction_signatures(&self) -> u64 {
141 match self {
142 Self::SimpleVote { .. } => 0,
143 Self::Transaction(usage_cost) => usage_cost
144 .transaction
145 .signature_details()
146 .num_secp256r1_instruction_signatures(),
147 }
148 }
149}
150
151#[derive(Debug)]
153pub struct UsageCostDetails<'a, Tx> {
154 pub transaction: &'a Tx,
155 pub signature_cost: u64,
156 pub write_lock_cost: u64,
157 pub data_bytes_cost: u64,
158 pub programs_execution_cost: u64,
159 pub loaded_accounts_data_size_cost: u64,
160 pub allocated_accounts_data_size: u64,
161}
162
163impl<Tx> UsageCostDetails<'_, Tx> {
164 pub fn sum(&self) -> u64 {
165 self.signature_cost
166 .saturating_add(self.write_lock_cost)
167 .saturating_add(self.data_bytes_cost)
168 .saturating_add(self.programs_execution_cost)
169 .saturating_add(self.loaded_accounts_data_size_cost)
170 }
171}
172
173#[cfg(feature = "dev-context-only-utils")]
174#[derive(Debug)]
175pub struct WritableKeysTransaction(pub Vec<Pubkey>);
176
177#[cfg(feature = "dev-context-only-utils")]
178impl solana_svm_transaction::svm_message::SVMMessage for WritableKeysTransaction {
179 fn num_transaction_signatures(&self) -> u64 {
180 unimplemented!("WritableKeysTransaction::num_transaction_signatures")
181 }
182
183 fn num_write_locks(&self) -> u64 {
184 unimplemented!("WritableKeysTransaction::num_write_locks")
185 }
186
187 fn recent_blockhash(&self) -> &solana_hash::Hash {
188 unimplemented!("WritableKeysTransaction::recent_blockhash")
189 }
190
191 fn num_instructions(&self) -> usize {
192 unimplemented!("WritableKeysTransaction::num_instructions")
193 }
194
195 fn instructions_iter(
196 &self,
197 ) -> impl Iterator<Item = solana_svm_transaction::instruction::SVMInstruction> {
198 core::iter::empty()
199 }
200
201 fn program_instructions_iter(
202 &self,
203 ) -> impl Iterator<Item = (&Pubkey, solana_svm_transaction::instruction::SVMInstruction)> + Clone
204 {
205 core::iter::empty()
206 }
207
208 fn account_keys(&self) -> solana_message::AccountKeys {
209 solana_message::AccountKeys::new(&self.0, None)
210 }
211
212 fn fee_payer(&self) -> &Pubkey {
213 unimplemented!("WritableKeysTransaction::fee_payer")
214 }
215
216 fn is_writable(&self, _index: usize) -> bool {
217 true
218 }
219
220 fn is_signer(&self, _index: usize) -> bool {
221 unimplemented!("WritableKeysTransaction::is_signer")
222 }
223
224 fn is_invoked(&self, _key_index: usize) -> bool {
225 unimplemented!("WritableKeysTransaction::is_invoked")
226 }
227
228 fn num_lookup_tables(&self) -> usize {
229 unimplemented!("WritableKeysTransaction::num_lookup_tables")
230 }
231
232 fn message_address_table_lookups(
233 &self,
234 ) -> impl Iterator<
235 Item = solana_svm_transaction::message_address_table_lookup::SVMMessageAddressTableLookup,
236 > {
237 core::iter::empty()
238 }
239}
240
241#[cfg(feature = "dev-context-only-utils")]
242impl solana_svm_transaction::svm_transaction::SVMTransaction for WritableKeysTransaction {
243 fn signature(&self) -> &solana_signature::Signature {
244 unimplemented!("WritableKeysTransaction::signature")
245 }
246
247 fn signatures(&self) -> &[solana_signature::Signature] {
248 unimplemented!("WritableKeysTransaction::signatures")
249 }
250}
251
252#[cfg(feature = "dev-context-only-utils")]
253impl solana_runtime_transaction::transaction_meta::StaticMeta for WritableKeysTransaction {
254 fn message_hash(&self) -> &solana_hash::Hash {
255 unimplemented!("WritableKeysTransaction::message_hash")
256 }
257
258 fn is_simple_vote_transaction(&self) -> bool {
259 unimplemented!("WritableKeysTransaction::is_simple_vote_transaction")
260 }
261
262 fn signature_details(&self) -> &solana_message::TransactionSignatureDetails {
263 const DUMMY: solana_message::TransactionSignatureDetails =
264 solana_message::TransactionSignatureDetails::new(0, 0, 0, 0);
265 &DUMMY
266 }
267
268 fn compute_budget_instruction_details(&self) -> &ComputeBudgetInstructionDetails {
269 unimplemented!("WritableKeysTransaction::compute_budget_instruction_details")
270 }
271}
272
273#[cfg(feature = "dev-context-only-utils")]
274impl solana_runtime_transaction::transaction_with_meta::TransactionWithMeta
275 for WritableKeysTransaction
276{
277 #[allow(refining_impl_trait)]
278 fn as_sanitized_transaction(
279 &self,
280 ) -> std::borrow::Cow<solana_transaction::sanitized::SanitizedTransaction> {
281 unimplemented!("WritableKeysTransaction::as_sanitized_transaction");
282 }
283
284 fn to_versioned_transaction(&self) -> solana_transaction::versioned::VersionedTransaction {
285 unimplemented!("WritableKeysTransaction::to_versioned_transaction")
286 }
287}
288
289#[cfg(test)]
290mod tests {
291 use {
292 super::*,
293 crate::cost_model::CostModel,
294 solana_feature_set::FeatureSet,
295 solana_hash::Hash,
296 solana_keypair::Keypair,
297 solana_message::SimpleAddressLoader,
298 solana_reserved_account_keys::ReservedAccountKeys,
299 solana_runtime_transaction::runtime_transaction::RuntimeTransaction,
300 solana_transaction::{sanitized::MessageHash, versioned::VersionedTransaction},
301 solana_vote_program::{vote_state::TowerSync, vote_transaction},
302 };
303
304 #[test]
305 fn test_vote_transaction_cost() {
306 solana_logger::setup();
307 let node_keypair = Keypair::new();
308 let vote_keypair = Keypair::new();
309 let auth_keypair = Keypair::new();
310 let transaction = vote_transaction::new_tower_sync_transaction(
311 TowerSync::default(),
312 Hash::default(),
313 &node_keypair,
314 &vote_keypair,
315 &auth_keypair,
316 None,
317 );
318
319 let vote_transaction = RuntimeTransaction::try_create(
321 VersionedTransaction::from(transaction.clone()),
322 MessageHash::Compute,
323 Some(true),
324 SimpleAddressLoader::Disabled,
325 &ReservedAccountKeys::empty_key_set(),
326 )
327 .unwrap();
328
329 let none_vote_transaction = RuntimeTransaction::try_create(
331 VersionedTransaction::from(transaction),
332 MessageHash::Compute,
333 Some(false),
334 SimpleAddressLoader::Disabled,
335 &ReservedAccountKeys::empty_key_set(),
336 )
337 .unwrap();
338
339 let expected_vote_cost = SIMPLE_VOTE_USAGE_COST;
341 let expected_none_vote_cost = 21443;
343
344 let vote_cost = CostModel::calculate_cost(&vote_transaction, &FeatureSet::all_enabled());
345 let none_vote_cost =
346 CostModel::calculate_cost(&none_vote_transaction, &FeatureSet::all_enabled());
347
348 assert_eq!(expected_vote_cost, vote_cost.sum());
349 assert_eq!(expected_none_vote_cost, none_vote_cost.sum());
350 }
351}