Separate vote cost (#33230)

* Separate simple-vote transaction cost from non-vote transaction cost

* remove is_simple_vote flag from transaction UsageCostDetails

* update test and comment

* set static usage cost for SimpleVote transaction
This commit is contained in:
Tao Zhu 2023-09-25 15:02:08 -05:00 committed by GitHub
parent 40f536010f
commit a41c15e47e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 244 additions and 129 deletions

View File

@ -737,7 +737,7 @@ mod tests {
unprocessed_transaction_storage::ThreadType,
},
crossbeam_channel::{unbounded, Receiver},
solana_cost_model::cost_model::CostModel,
solana_cost_model::{cost_model::CostModel, transaction_cost::TransactionCost},
solana_entry::entry::{next_entry, next_versioned_entry},
solana_ledger::{
blockstore::{entries_to_test_shreds, Blockstore},
@ -1264,7 +1264,9 @@ mod tests {
};
let mut cost = CostModel::calculate_cost(&transactions[0], &bank.feature_set);
cost.bpf_execution_cost = actual_bpf_execution_cost;
if let TransactionCost::Transaction(ref mut usage_cost) = cost {
usage_cost.bpf_execution_cost = actual_bpf_execution_cost;
}
block_cost + cost.sum()
} else {

View File

@ -318,25 +318,25 @@ impl QosService {
Ok(cost) => {
saturating_add_assign!(
batched_transaction_details.costs.batched_signature_cost,
cost.signature_cost
cost.signature_cost()
);
saturating_add_assign!(
batched_transaction_details.costs.batched_write_lock_cost,
cost.write_lock_cost
cost.write_lock_cost()
);
saturating_add_assign!(
batched_transaction_details.costs.batched_data_bytes_cost,
cost.data_bytes_cost
cost.data_bytes_cost()
);
saturating_add_assign!(
batched_transaction_details
.costs
.batched_builtins_execute_cost,
cost.builtins_execution_cost
cost.builtins_execution_cost()
);
saturating_add_assign!(
batched_transaction_details.costs.batched_bpf_execute_cost,
cost.bpf_execution_cost
cost.bpf_execution_cost()
);
}
Err(transaction_error) => match transaction_error {
@ -589,6 +589,7 @@ mod tests {
use {
super::*,
itertools::Itertools,
solana_cost_model::transaction_cost::UsageCostDetails,
solana_runtime::genesis_utils::{create_genesis_config, GenesisConfigInfo},
solana_sdk::{
hash::Hash,
@ -734,7 +735,7 @@ mod tests {
let commited_status: Vec<CommitTransactionDetails> = qos_cost_results
.iter()
.map(|tx_cost| CommitTransactionDetails::Committed {
compute_units: tx_cost.as_ref().unwrap().bpf_execution_cost
compute_units: tx_cost.as_ref().unwrap().bpf_execution_cost()
+ execute_units_adjustment,
})
.collect();
@ -861,7 +862,7 @@ mod tests {
CommitTransactionDetails::NotCommitted
} else {
CommitTransactionDetails::Committed {
compute_units: tx_cost.as_ref().unwrap().bpf_execution_cost
compute_units: tx_cost.as_ref().unwrap().bpf_execution_cost()
+ execute_units_adjustment,
}
}
@ -904,14 +905,14 @@ mod tests {
let tx_cost_results: Vec<_> = (0..num_txs)
.map(|n| {
if n % 2 == 0 {
Ok(TransactionCost {
Ok(TransactionCost::Transaction(UsageCostDetails {
signature_cost,
write_lock_cost,
data_bytes_cost,
builtins_execution_cost,
bpf_execution_cost,
..TransactionCost::default()
})
..UsageCostDetails::default()
}))
} else {
Err(TransactionError::WouldExceedMaxBlockCostLimit)
}

View File

@ -6,7 +6,7 @@
//!
use {
crate::{block_cost_limits::*, transaction_cost::TransactionCost},
crate::{block_cost_limits::*, transaction_cost::*},
log::*,
solana_program_runtime::compute_budget::{
ComputeBudget, DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT, MAX_COMPUTE_UNIT_LIMIT,
@ -36,16 +36,21 @@ impl CostModel {
transaction: &SanitizedTransaction,
feature_set: &FeatureSet,
) -> TransactionCost {
let mut tx_cost = TransactionCost::new_with_default_capacity();
if transaction.is_simple_vote_transaction() {
TransactionCost::SimpleVote {
writable_accounts: Self::get_writable_accounts(transaction),
}
} else {
let mut tx_cost = UsageCostDetails::new_with_default_capacity();
tx_cost.signature_cost = Self::get_signature_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);
tx_cost.is_simple_vote = transaction.is_simple_vote_transaction();
tx_cost.signature_cost = Self::get_signature_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);
debug!("transaction {:?} has cost {:?}", transaction, tx_cost);
tx_cost
debug!("transaction {:?} has cost {:?}", transaction, tx_cost);
TransactionCost::Transaction(tx_cost)
}
}
// Calculate cost of loaded accounts size in the same way heap cost is charged at
@ -68,24 +73,30 @@ impl CostModel {
transaction.signatures().len() as u64 * SIGNATURE_COST
}
fn get_write_lock_cost(tx_cost: &mut TransactionCost, transaction: &SanitizedTransaction) {
fn get_writable_accounts(transaction: &SanitizedTransaction) -> Vec<Pubkey> {
let message = transaction.message();
message
.account_keys()
.iter()
.enumerate()
.for_each(|(i, k)| {
let is_writable = message.is_writable(i);
if is_writable {
tx_cost.writable_accounts.push(*k);
tx_cost.write_lock_cost += WRITE_LOCK_UNITS;
.filter_map(|(i, k)| {
if message.is_writable(i) {
Some(*k)
} else {
None
}
});
})
.collect()
}
fn get_write_lock_cost(tx_cost: &mut UsageCostDetails, transaction: &SanitizedTransaction) {
tx_cost.writable_accounts = Self::get_writable_accounts(transaction);
tx_cost.write_lock_cost =
WRITE_LOCK_UNITS.saturating_mul(tx_cost.writable_accounts.len() as u64);
}
fn get_transaction_cost(
tx_cost: &mut TransactionCost,
tx_cost: &mut UsageCostDetails,
transaction: &SanitizedTransaction,
feature_set: &FeatureSet,
) {
@ -298,7 +309,7 @@ mod tests {
.get(&system_program::id())
.unwrap();
let mut tx_cost = TransactionCost::default();
let mut tx_cost = UsageCostDetails::default();
CostModel::get_transaction_cost(
&mut tx_cost,
&simple_transaction,
@ -327,7 +338,7 @@ mod tests {
let token_transaction = SanitizedTransaction::from_transaction_for_tests(tx);
debug!("token_transaction {:?}", token_transaction);
let mut tx_cost = TransactionCost::default();
let mut tx_cost = UsageCostDetails::default();
CostModel::get_transaction_cost(
&mut tx_cost,
&token_transaction,
@ -364,7 +375,7 @@ mod tests {
);
let token_transaction = SanitizedTransaction::from_transaction_for_tests(tx);
let mut tx_cost = TransactionCost::default();
let mut tx_cost = UsageCostDetails::default();
CostModel::get_transaction_cost(
&mut tx_cost,
&token_transaction,
@ -414,7 +425,7 @@ mod tests {
);
let token_transaction = SanitizedTransaction::from_transaction_for_tests(tx);
let mut tx_cost = TransactionCost::default();
let mut tx_cost = UsageCostDetails::default();
CostModel::get_transaction_cost(
&mut tx_cost,
&token_transaction,
@ -446,7 +457,7 @@ mod tests {
.unwrap();
let expected_cost = program_cost * 2;
let mut tx_cost = TransactionCost::default();
let mut tx_cost = UsageCostDetails::default();
CostModel::get_transaction_cost(&mut tx_cost, &tx, &FeatureSet::all_enabled());
assert_eq!(expected_cost, tx_cost.builtins_execution_cost);
assert_eq!(0, tx_cost.bpf_execution_cost);
@ -478,7 +489,7 @@ mod tests {
debug!("many random transaction {:?}", tx);
let expected_cost = DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64 * 2;
let mut tx_cost = TransactionCost::default();
let mut tx_cost = UsageCostDetails::default();
CostModel::get_transaction_cost(&mut tx_cost, &tx, &FeatureSet::all_enabled());
assert_eq!(0, tx_cost.builtins_execution_cost);
assert_eq!(expected_cost, tx_cost.bpf_execution_cost);
@ -509,11 +520,11 @@ 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]);
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]);
}
#[test]
@ -539,12 +550,12 @@ mod tests {
* DEFAULT_PAGE_COST;
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.builtins_execution_cost);
assert_eq!(2, tx_cost.writable_accounts.len());
assert_eq!(expected_account_cost, tx_cost.write_lock_cost());
assert_eq!(*expected_execution_cost, tx_cost.builtins_execution_cost());
assert_eq!(2, tx_cost.writable_accounts().len());
assert_eq!(
expected_loaded_accounts_data_size_cost,
tx_cost.loaded_accounts_data_size_cost
tx_cost.loaded_accounts_data_size_cost()
);
}
@ -568,12 +579,12 @@ mod tests {
let expected_loaded_accounts_data_size_cost = 0;
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.builtins_execution_cost);
assert_eq!(2, tx_cost.writable_accounts.len());
assert_eq!(expected_account_cost, tx_cost.write_lock_cost());
assert_eq!(*expected_execution_cost, tx_cost.builtins_execution_cost());
assert_eq!(2, tx_cost.writable_accounts().len());
assert_eq!(
expected_loaded_accounts_data_size_cost,
tx_cost.loaded_accounts_data_size_cost
tx_cost.loaded_accounts_data_size_cost()
);
}
@ -607,12 +618,12 @@ mod tests {
let expected_loaded_accounts_data_size_cost = (data_limit as u64) / (32 * 1024) * 8;
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.builtins_execution_cost);
assert_eq!(2, tx_cost.writable_accounts.len());
assert_eq!(expected_account_cost, tx_cost.write_lock_cost());
assert_eq!(expected_execution_cost, tx_cost.builtins_execution_cost());
assert_eq!(2, tx_cost.writable_accounts().len());
assert_eq!(
expected_loaded_accounts_data_size_cost,
tx_cost.loaded_accounts_data_size_cost
tx_cost.loaded_accounts_data_size_cost()
);
}
@ -640,12 +651,12 @@ mod tests {
let expected_loaded_accounts_data_size_cost = 0;
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.builtins_execution_cost);
assert_eq!(2, tx_cost.writable_accounts.len());
assert_eq!(expected_account_cost, tx_cost.write_lock_cost());
assert_eq!(expected_execution_cost, tx_cost.builtins_execution_cost());
assert_eq!(2, tx_cost.writable_accounts().len());
assert_eq!(
expected_loaded_accounts_data_size_cost,
tx_cost.loaded_accounts_data_size_cost
tx_cost.loaded_accounts_data_size_cost()
);
}
@ -705,7 +716,7 @@ mod tests {
.unwrap();
let expected_bpf_cost = DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT;
let mut tx_cost = TransactionCost::default();
let mut tx_cost = UsageCostDetails::default();
CostModel::get_transaction_cost(&mut tx_cost, &transaction, &FeatureSet::all_enabled());
assert_eq!(expected_builtin_cost, tx_cost.builtins_execution_cost);

View File

@ -119,7 +119,7 @@ impl CostTracker {
estimated_tx_cost: &TransactionCost,
actual_execution_units: u64,
) {
let estimated_execution_units = estimated_tx_cost.bpf_execution_cost;
let estimated_execution_units = estimated_tx_cost.bpf_execution_cost();
match actual_execution_units.cmp(&estimated_execution_units) {
Ordering::Equal => (),
Ordering::Greater => {
@ -180,18 +180,17 @@ impl CostTracker {
fn would_fit(&self, tx_cost: &TransactionCost) -> Result<(), CostTrackerError> {
let cost: u64 = tx_cost.sum();
let vote_cost = if tx_cost.is_simple_vote { cost } else { 0 };
// check against the total package cost
if self.block_cost.saturating_add(cost) > self.block_cost_limit {
if tx_cost.is_simple_vote() {
// if vote transaction, check if it exceeds vote_transaction_limit
if self.vote_cost.saturating_add(cost) > self.vote_cost_limit {
return Err(CostTrackerError::WouldExceedVoteMaxLimit);
}
} else if self.block_cost.saturating_add(cost) > self.block_cost_limit {
// check against the total package cost
return Err(CostTrackerError::WouldExceedBlockMaxLimit);
}
// if vote transaction, check if it exceeds vote_transaction_limit
if self.vote_cost.saturating_add(vote_cost) > self.vote_cost_limit {
return Err(CostTrackerError::WouldExceedVoteMaxLimit);
}
// check if the transaction itself is more costly than the account_cost_limit
if cost > self.account_cost_limit {
return Err(CostTrackerError::WouldExceedAccountMaxLimit);
@ -201,7 +200,7 @@ impl CostTracker {
// size. This way, transactions are not unnecessarily retried.
let account_data_size = self
.account_data_size
.saturating_add(tx_cost.account_data_size);
.saturating_add(tx_cost.account_data_size());
if let Some(account_data_size_limit) = self.account_data_size_limit {
if account_data_size > account_data_size_limit {
return Err(CostTrackerError::WouldExceedAccountDataTotalLimit);
@ -213,7 +212,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().iter() {
match self.cost_by_writable_accounts.get(account_key) {
Some(chained_cost) => {
if chained_cost.saturating_add(cost) > self.account_cost_limit {
@ -231,7 +230,7 @@ impl CostTracker {
fn add_transaction_cost(&mut self, tx_cost: &TransactionCost) {
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.account_data_size, tx_cost.account_data_size());
saturating_add_assign!(self.transaction_count, 1);
}
@ -240,13 +239,13 @@ impl CostTracker {
self.sub_transaction_execution_cost(tx_cost, cost);
self.account_data_size = self
.account_data_size
.saturating_sub(tx_cost.account_data_size);
.saturating_sub(tx_cost.account_data_size());
self.transaction_count = self.transaction_count.saturating_sub(1);
}
/// Apply additional actual execution units to cost_tracker
fn add_transaction_execution_cost(&mut self, tx_cost: &TransactionCost, adjustment: u64) {
for account_key in tx_cost.writable_accounts.iter() {
for account_key in tx_cost.writable_accounts().iter() {
let account_cost = self
.cost_by_writable_accounts
.entry(*account_key)
@ -254,14 +253,14 @@ impl CostTracker {
*account_cost = account_cost.saturating_add(adjustment);
}
self.block_cost = self.block_cost.saturating_add(adjustment);
if tx_cost.is_simple_vote {
if tx_cost.is_simple_vote() {
self.vote_cost = self.vote_cost.saturating_add(adjustment);
}
}
/// 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() {
for account_key in tx_cost.writable_accounts().iter() {
let account_cost = self
.cost_by_writable_accounts
.entry(*account_key)
@ -269,7 +268,7 @@ impl CostTracker {
*account_cost = account_cost.saturating_sub(adjustment);
}
self.block_cost = self.block_cost.saturating_sub(adjustment);
if tx_cost.is_simple_vote {
if tx_cost.is_simple_vote() {
self.vote_cost = self.vote_cost.saturating_sub(adjustment);
}
}
@ -287,6 +286,7 @@ impl CostTracker {
mod tests {
use {
super::*,
crate::transaction_cost::*,
solana_sdk::{
hash::Hash,
signature::{Keypair, Signer},
@ -331,11 +331,11 @@ mod tests {
let simple_transaction = SanitizedTransaction::from_transaction_for_tests(
system_transaction::transfer(mint_keypair, &keypair.pubkey(), 2, *start_hash),
);
let mut tx_cost = TransactionCost::new_with_capacity(1);
let mut tx_cost = UsageCostDetails::new_with_capacity(1);
tx_cost.bpf_execution_cost = 5;
tx_cost.writable_accounts.push(mint_keypair.pubkey());
(simple_transaction, tx_cost)
(simple_transaction, TransactionCost::Transaction(tx_cost))
}
fn build_simple_vote_transaction(
@ -359,12 +359,12 @@ mod tests {
SimpleAddressLoader::Disabled,
)
.unwrap();
let mut tx_cost = TransactionCost::new_with_capacity(1);
tx_cost.builtins_execution_cost = 10;
tx_cost.writable_accounts.push(mint_keypair.pubkey());
tx_cost.is_simple_vote = true;
(vote_transaction, tx_cost)
let writable_accounts = vec![mint_keypair.pubkey()];
(
vote_transaction,
TransactionCost::SimpleVote { writable_accounts },
)
}
#[test]
@ -413,7 +413,11 @@ mod tests {
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);
tx_cost.account_data_size = 1;
if let TransactionCost::Transaction(ref mut usage_cost) = tx_cost {
usage_cost.account_data_size = 1;
} else {
unreachable!();
}
let cost = tx_cost.sum();
// build testee to have capacity for one simple transaction
@ -568,8 +572,16 @@ mod tests {
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);
tx_cost1.account_data_size = MAX_BLOCK_ACCOUNTS_DATA_SIZE_DELTA;
tx_cost2.account_data_size = MAX_BLOCK_ACCOUNTS_DATA_SIZE_DELTA + 1;
if let TransactionCost::Transaction(ref mut usage_cost) = tx_cost1 {
usage_cost.account_data_size = MAX_BLOCK_ACCOUNTS_DATA_SIZE_DELTA;
} else {
unreachable!();
}
if let TransactionCost::Transaction(ref mut usage_cost) = tx_cost2 {
usage_cost.account_data_size = MAX_BLOCK_ACCOUNTS_DATA_SIZE_DELTA + 1;
} else {
unreachable!();
}
let cost1 = tx_cost1.sum();
let cost2 = tx_cost2.sum();
@ -596,8 +608,16 @@ mod tests {
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 remaining_account_data_size = 1234;
tx_cost1.account_data_size = remaining_account_data_size;
tx_cost2.account_data_size = remaining_account_data_size + 1;
if let TransactionCost::Transaction(ref mut usage_cost) = tx_cost1 {
usage_cost.account_data_size = remaining_account_data_size;
} else {
unreachable!();
}
if let TransactionCost::Transaction(ref mut usage_cost) = tx_cost2 {
usage_cost.account_data_size = remaining_account_data_size + 1;
} else {
unreachable!();
}
let cost1 = tx_cost1.sum();
let cost2 = tx_cost2.sum();
@ -661,11 +681,11 @@ mod tests {
// | acct3 | $cost |
// and block_cost = $cost
{
let tx_cost = TransactionCost {
let tx_cost = TransactionCost::Transaction(UsageCostDetails {
writable_accounts: vec![acct1, acct2, acct3],
bpf_execution_cost: cost,
..TransactionCost::default()
};
..UsageCostDetails::default()
});
assert!(testee.try_add(&tx_cost).is_ok());
let (_costliest_account, costliest_account_cost) = testee.find_costliest_account();
assert_eq!(cost, testee.block_cost);
@ -679,11 +699,11 @@ mod tests {
// | acct3 | $cost |
// and block_cost = $cost * 2
{
let tx_cost = TransactionCost {
let tx_cost = TransactionCost::Transaction(UsageCostDetails {
writable_accounts: vec![acct2],
bpf_execution_cost: cost,
..TransactionCost::default()
};
..UsageCostDetails::default()
});
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);
@ -699,11 +719,11 @@ mod tests {
// | acct3 | $cost |
// and block_cost = $cost * 2
{
let tx_cost = TransactionCost {
let tx_cost = TransactionCost::Transaction(UsageCostDetails {
writable_accounts: vec![acct1, acct2],
bpf_execution_cost: cost,
..TransactionCost::default()
};
..UsageCostDetails::default()
});
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);
@ -723,11 +743,11 @@ mod tests {
let block_max = account_max * 3; // for three accts
let mut testee = CostTracker::new(account_max, block_max, block_max, None);
let tx_cost = TransactionCost {
let tx_cost = TransactionCost::Transaction(UsageCostDetails {
writable_accounts: vec![acct1, acct2, acct3],
bpf_execution_cost: cost,
..TransactionCost::default()
};
..UsageCostDetails::default()
});
let mut expected_block_cost = tx_cost.sum();
let expected_tx_count = 1;
assert!(testee.try_add(&tx_cost).is_ok());
@ -810,11 +830,11 @@ mod tests {
let acct3 = Pubkey::new_unique();
let cost = 100;
let tx_cost = TransactionCost {
let tx_cost = TransactionCost::Transaction(UsageCostDetails {
writable_accounts: vec![acct1, acct2, acct3],
bpf_execution_cost: cost,
..TransactionCost::default()
};
..UsageCostDetails::default()
});
let mut cost_tracker = CostTracker::default();
@ -857,11 +877,11 @@ mod tests {
let mut cost_tracker = CostTracker::default();
let cost = 100u64;
let tx_cost = TransactionCost {
let tx_cost = TransactionCost::Transaction(UsageCostDetails {
writable_accounts: vec![Pubkey::new_unique()],
bpf_execution_cost: cost,
..TransactionCost::default()
};
..UsageCostDetails::default()
});
cost_tracker.add_transaction_cost(&tx_cost);
// assert cost_tracker is reverted to default

View File

@ -1,10 +1,99 @@
use solana_sdk::pubkey::Pubkey;
use {crate::block_cost_limits, solana_sdk::pubkey::Pubkey};
/// TransactionCost is used to represent resources required to process
/// a transaction, denominated in CU (eg. Compute Units).
/// Resources required to process a regular transaction often include
/// an array of variables, such as execution cost, loaded bytes, write
/// lock and read lock etc.
/// SimpleVote has a simpler and pre-determined format: it has 1 or 2 signatures,
/// 2 write locks, a vote instruction and less than 32k (page size) accounts to load.
/// It's cost therefore can be static #33269.
const SIMPLE_VOTE_USAGE_COST: u64 = 3428;
#[derive(Debug)]
pub enum TransactionCost {
SimpleVote { writable_accounts: Vec<Pubkey> },
Transaction(UsageCostDetails),
}
impl TransactionCost {
pub fn sum(&self) -> u64 {
match self {
Self::SimpleVote { .. } => SIMPLE_VOTE_USAGE_COST,
Self::Transaction(usage_cost) => usage_cost.sum(),
}
}
pub fn bpf_execution_cost(&self) -> u64 {
match self {
Self::SimpleVote { .. } => 0,
Self::Transaction(usage_cost) => usage_cost.bpf_execution_cost,
}
}
pub fn is_simple_vote(&self) -> bool {
match self {
Self::SimpleVote { .. } => true,
Self::Transaction(_) => false,
}
}
pub fn data_bytes_cost(&self) -> u64 {
match self {
Self::SimpleVote { .. } => 0,
Self::Transaction(usage_cost) => usage_cost.data_bytes_cost,
}
}
pub fn account_data_size(&self) -> u64 {
match self {
Self::SimpleVote { .. } => 0,
Self::Transaction(usage_cost) => usage_cost.account_data_size,
}
}
pub fn loaded_accounts_data_size_cost(&self) -> u64 {
match self {
Self::SimpleVote { .. } => 8, // simple-vote loads less than 32K account data,
// the cost round up to be one page (32K) cost: 8CU
Self::Transaction(usage_cost) => usage_cost.loaded_accounts_data_size_cost,
}
}
pub fn signature_cost(&self) -> u64 {
match self {
Self::SimpleVote { .. } => block_cost_limits::SIGNATURE_COST,
Self::Transaction(usage_cost) => usage_cost.signature_cost,
}
}
pub fn write_lock_cost(&self) -> u64 {
match self {
Self::SimpleVote { .. } => block_cost_limits::WRITE_LOCK_UNITS.saturating_mul(2),
Self::Transaction(usage_cost) => usage_cost.write_lock_cost,
}
}
pub fn builtins_execution_cost(&self) -> u64 {
match self {
Self::SimpleVote { .. } => solana_vote_program::vote_processor::DEFAULT_COMPUTE_UNITS,
Self::Transaction(usage_cost) => usage_cost.builtins_execution_cost,
}
}
pub fn writable_accounts(&self) -> &[Pubkey] {
match self {
Self::SimpleVote { writable_accounts } => writable_accounts,
Self::Transaction(usage_cost) => &usage_cost.writable_accounts,
}
}
}
const MAX_WRITABLE_ACCOUNTS: usize = 256;
// costs are stored in number of 'compute unit's
#[derive(Debug)]
pub struct TransactionCost {
pub struct UsageCostDetails {
pub writable_accounts: Vec<Pubkey>,
pub signature_cost: u64,
pub write_lock_cost: u64,
@ -13,10 +102,9 @@ pub struct TransactionCost {
pub bpf_execution_cost: u64,
pub loaded_accounts_data_size_cost: u64,
pub account_data_size: u64,
pub is_simple_vote: bool,
}
impl Default for TransactionCost {
impl Default for UsageCostDetails {
fn default() -> Self {
Self {
writable_accounts: Vec::with_capacity(MAX_WRITABLE_ACCOUNTS),
@ -27,13 +115,12 @@ impl Default for TransactionCost {
bpf_execution_cost: 0u64,
loaded_accounts_data_size_cost: 0u64,
account_data_size: 0u64,
is_simple_vote: false,
}
}
}
#[cfg(test)]
impl PartialEq for TransactionCost {
impl PartialEq for UsageCostDetails {
fn eq(&self, other: &Self) -> bool {
fn to_hash_set(v: &[Pubkey]) -> std::collections::HashSet<&Pubkey> {
v.iter().collect()
@ -46,15 +133,15 @@ impl PartialEq for TransactionCost {
&& 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.is_simple_vote == other.is_simple_vote
&& to_hash_set(&self.writable_accounts) == to_hash_set(&other.writable_accounts)
}
}
#[cfg(test)]
impl Eq for TransactionCost {}
impl Eq for UsageCostDetails {}
impl TransactionCost {
impl UsageCostDetails {
#[cfg(test)]
pub fn new_with_capacity(capacity: usize) -> Self {
Self {
writable_accounts: Vec::with_capacity(capacity),
@ -67,25 +154,19 @@ impl TransactionCost {
}
pub fn sum(&self) -> u64 {
if self.is_simple_vote {
self.signature_cost
.saturating_add(self.write_lock_cost)
.saturating_add(self.data_bytes_cost)
.saturating_add(self.builtins_execution_cost)
} else {
self.signature_cost
.saturating_add(self.write_lock_cost)
.saturating_add(self.data_bytes_cost)
.saturating_add(self.builtins_execution_cost)
.saturating_add(self.bpf_execution_cost)
.saturating_add(self.loaded_accounts_data_size_cost)
}
self.signature_cost
.saturating_add(self.write_lock_cost)
.saturating_add(self.data_bytes_cost)
.saturating_add(self.builtins_execution_cost)
.saturating_add(self.bpf_execution_cost)
.saturating_add(self.loaded_accounts_data_size_cost)
}
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::cost_model::CostModel,
solana_sdk::{
feature_set::FeatureSet,
@ -131,8 +212,8 @@ mod tests {
)
.unwrap();
// expected vote tx cost: 2 write locks, 2 sig, 1 vite ix, and 11 CU tx data cost
let expected_vote_cost = 4151;
// expected vote tx cost: 2 write locks, 1 sig, 1 vote ix, 8cu of loaded accounts size,
let expected_vote_cost = SIMPLE_VOTE_USAGE_COST;
// expected non-vote tx cost would include default loaded accounts size cost (16384) additionally
let expected_none_vote_cost = 20535;