1use {
2 crate::block_cost_limits,
3 solana_sdk::{message::TransactionSignatureDetails, pubkey::Pubkey},
4 solana_svm_transaction::svm_message::SVMMessage,
5};
6
7const SIMPLE_VOTE_USAGE_COST: u64 = 3428;
16
17#[derive(Debug)]
18pub enum TransactionCost<'a, Tx: SVMMessage> {
19 SimpleVote { transaction: &'a Tx },
20 Transaction(UsageCostDetails<'a, Tx>),
21}
22
23impl<'a, Tx: SVMMessage> TransactionCost<'a, Tx> {
24 pub fn sum(&self) -> u64 {
25 #![allow(clippy::assertions_on_constants)]
26 match self {
27 Self::SimpleVote { .. } => {
28 const _: () = assert!(
29 SIMPLE_VOTE_USAGE_COST
30 == solana_vote_program::vote_processor::DEFAULT_COMPUTE_UNITS
31 + block_cost_limits::SIGNATURE_COST
32 + 2 * block_cost_limits::WRITE_LOCK_UNITS
33 + 8
34 );
35
36 SIMPLE_VOTE_USAGE_COST
37 }
38 Self::Transaction(usage_cost) => usage_cost.sum(),
39 }
40 }
41
42 pub fn programs_execution_cost(&self) -> u64 {
43 match self {
44 Self::SimpleVote { .. } => solana_vote_program::vote_processor::DEFAULT_COMPUTE_UNITS,
45 Self::Transaction(usage_cost) => usage_cost.programs_execution_cost,
46 }
47 }
48
49 pub fn is_simple_vote(&self) -> bool {
50 match self {
51 Self::SimpleVote { .. } => true,
52 Self::Transaction(_) => false,
53 }
54 }
55
56 pub fn data_bytes_cost(&self) -> u64 {
57 match self {
58 Self::SimpleVote { .. } => 0,
59 Self::Transaction(usage_cost) => usage_cost.data_bytes_cost,
60 }
61 }
62
63 pub fn allocated_accounts_data_size(&self) -> u64 {
64 match self {
65 Self::SimpleVote { .. } => 0,
66 Self::Transaction(usage_cost) => usage_cost.allocated_accounts_data_size,
67 }
68 }
69
70 pub fn loaded_accounts_data_size_cost(&self) -> u64 {
71 match self {
72 Self::SimpleVote { .. } => 8, Self::Transaction(usage_cost) => usage_cost.loaded_accounts_data_size_cost,
75 }
76 }
77
78 pub fn signature_cost(&self) -> u64 {
79 match self {
80 Self::SimpleVote { .. } => block_cost_limits::SIGNATURE_COST,
81 Self::Transaction(usage_cost) => usage_cost.signature_cost,
82 }
83 }
84
85 pub fn write_lock_cost(&self) -> u64 {
86 match self {
87 Self::SimpleVote { .. } => block_cost_limits::WRITE_LOCK_UNITS.saturating_mul(2),
88 Self::Transaction(usage_cost) => usage_cost.write_lock_cost,
89 }
90 }
91
92 pub fn writable_accounts(&self) -> impl Iterator<Item = &Pubkey> {
93 let transaction = match self {
94 Self::SimpleVote { transaction } => transaction,
95 Self::Transaction(usage_cost) => usage_cost.transaction,
96 };
97 transaction
98 .account_keys()
99 .iter()
100 .enumerate()
101 .filter_map(|(index, key)| transaction.is_writable(index).then_some(key))
102 }
103
104 pub fn num_transaction_signatures(&self) -> u64 {
105 match self {
106 Self::SimpleVote { .. } => 1,
107 Self::Transaction(usage_cost) => {
108 usage_cost.signature_details.num_transaction_signatures()
109 }
110 }
111 }
112
113 pub fn num_secp256k1_instruction_signatures(&self) -> u64 {
114 match self {
115 Self::SimpleVote { .. } => 0,
116 Self::Transaction(usage_cost) => usage_cost
117 .signature_details
118 .num_secp256k1_instruction_signatures(),
119 }
120 }
121
122 pub fn num_ed25519_instruction_signatures(&self) -> u64 {
123 match self {
124 Self::SimpleVote { .. } => 0,
125 Self::Transaction(usage_cost) => usage_cost
126 .signature_details
127 .num_ed25519_instruction_signatures(),
128 }
129 }
130}
131
132#[derive(Debug)]
134pub struct UsageCostDetails<'a, Tx: SVMMessage> {
135 pub transaction: &'a Tx,
136 pub signature_cost: u64,
137 pub write_lock_cost: u64,
138 pub data_bytes_cost: u64,
139 pub programs_execution_cost: u64,
140 pub loaded_accounts_data_size_cost: u64,
141 pub allocated_accounts_data_size: u64,
142 pub signature_details: TransactionSignatureDetails,
143}
144
145impl<'a, Tx: SVMMessage> UsageCostDetails<'a, Tx> {
146 pub fn sum(&self) -> u64 {
147 self.signature_cost
148 .saturating_add(self.write_lock_cost)
149 .saturating_add(self.data_bytes_cost)
150 .saturating_add(self.programs_execution_cost)
151 .saturating_add(self.loaded_accounts_data_size_cost)
152 }
153}
154
155#[cfg(feature = "dev-context-only-utils")]
156#[derive(Debug)]
157pub struct WritableKeysTransaction(pub Vec<Pubkey>);
158
159#[cfg(feature = "dev-context-only-utils")]
160impl SVMMessage for WritableKeysTransaction {
161 fn num_total_signatures(&self) -> u64 {
162 unimplemented!("WritableKeysTransaction::num_total_signatures")
163 }
164
165 fn num_write_locks(&self) -> u64 {
166 unimplemented!("WritableKeysTransaction::num_write_locks")
167 }
168
169 fn recent_blockhash(&self) -> &solana_sdk::hash::Hash {
170 unimplemented!("WritableKeysTransaction::recent_blockhash")
171 }
172
173 fn num_instructions(&self) -> usize {
174 unimplemented!("WritableKeysTransaction::num_instructions")
175 }
176
177 fn instructions_iter(
178 &self,
179 ) -> impl Iterator<Item = solana_svm_transaction::instruction::SVMInstruction> {
180 core::iter::empty()
181 }
182
183 fn program_instructions_iter(
184 &self,
185 ) -> impl Iterator<Item = (&Pubkey, solana_svm_transaction::instruction::SVMInstruction)> + Clone
186 {
187 core::iter::empty()
188 }
189
190 fn account_keys(&self) -> solana_sdk::message::AccountKeys {
191 solana_sdk::message::AccountKeys::new(&self.0, None)
192 }
193
194 fn fee_payer(&self) -> &Pubkey {
195 unimplemented!("WritableKeysTransaction::fee_payer")
196 }
197
198 fn is_writable(&self, _index: usize) -> bool {
199 true
200 }
201
202 fn is_signer(&self, _index: usize) -> bool {
203 unimplemented!("WritableKeysTransaction::is_signer")
204 }
205
206 fn is_invoked(&self, _key_index: usize) -> bool {
207 unimplemented!("WritableKeysTransaction::is_invoked")
208 }
209
210 fn num_lookup_tables(&self) -> usize {
211 unimplemented!("WritableKeysTransaction::num_lookup_tables")
212 }
213
214 fn message_address_table_lookups(
215 &self,
216 ) -> impl Iterator<
217 Item = solana_svm_transaction::message_address_table_lookup::SVMMessageAddressTableLookup,
218 > {
219 core::iter::empty()
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use {
226 super::*,
227 crate::cost_model::CostModel,
228 solana_feature_set::FeatureSet,
229 solana_sdk::{
230 hash::Hash,
231 message::SimpleAddressLoader,
232 reserved_account_keys::ReservedAccountKeys,
233 signer::keypair::Keypair,
234 transaction::{MessageHash, SanitizedTransaction, VersionedTransaction},
235 },
236 solana_vote_program::{vote_state::TowerSync, vote_transaction},
237 };
238
239 #[test]
240 fn test_vote_transaction_cost() {
241 solana_logger::setup();
242 let node_keypair = Keypair::new();
243 let vote_keypair = Keypair::new();
244 let auth_keypair = Keypair::new();
245 let transaction = vote_transaction::new_tower_sync_transaction(
246 TowerSync::default(),
247 Hash::default(),
248 &node_keypair,
249 &vote_keypair,
250 &auth_keypair,
251 None,
252 );
253
254 let vote_transaction = SanitizedTransaction::try_create(
256 VersionedTransaction::from(transaction.clone()),
257 MessageHash::Compute,
258 Some(true),
259 SimpleAddressLoader::Disabled,
260 &ReservedAccountKeys::empty_key_set(),
261 )
262 .unwrap();
263
264 let none_vote_transaction = SanitizedTransaction::try_create(
266 VersionedTransaction::from(transaction),
267 MessageHash::Compute,
268 Some(false),
269 SimpleAddressLoader::Disabled,
270 &ReservedAccountKeys::empty_key_set(),
271 )
272 .unwrap();
273
274 let expected_vote_cost = SIMPLE_VOTE_USAGE_COST;
276 let expected_none_vote_cost = 21443;
278
279 let vote_cost = CostModel::calculate_cost(&vote_transaction, &FeatureSet::all_enabled());
280 let none_vote_cost =
281 CostModel::calculate_cost(&none_vote_transaction, &FeatureSet::all_enabled());
282
283 assert_eq!(expected_vote_cost, vote_cost.sum());
284 assert_eq!(expected_none_vote_cost, none_vote_cost.sum());
285 }
286}