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:
parent
40f536010f
commit
a41c15e47e
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in New Issue