Apply transaction actual execution units to cost_tracker (#24311)
* Pass the sum of consumed compute units to cost_tracker * cost model tracks builtins and bpf programs separately, enabling adjust block cost by actual bpf programs execution costs * Copied nightly-only experimental `checked_add_(un)signed` implementation to sdk * Add function to update cost tracker with execution cost adjustment * Review suggestion - using enum instead of struct for CommitTransactionDetails Co-authored-by: Justin Starry <justin.m.starry@gmail.com> * review - rename variable to distinguish accumulated_consumed_units from individual compute_units_consumed * not to use signed integer operations * Review - using saturating_add_assign!(), and checked_*().unwrap_or() * Review - using Ordering enum to cmp * replace checked_ with saturating_ * review - remove unnecessary Option<> * Review - add function to report number of non-zero units account to metrics
This commit is contained in:
parent
fed13b1f62
commit
a21fc3f303
|
@ -100,6 +100,12 @@ struct RecordTransactionsSummary {
|
||||||
retryable_indexes: Vec<usize>,
|
retryable_indexes: Vec<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum CommitTransactionDetails {
|
||||||
|
Committed { compute_units: u64 },
|
||||||
|
NotCommitted,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ExecuteAndCommitTransactionsOutput {
|
pub struct ExecuteAndCommitTransactionsOutput {
|
||||||
// Total number of transactions that were passed as candidates for execution
|
// Total number of transactions that were passed as candidates for execution
|
||||||
transactions_attempted_execution_count: usize,
|
transactions_attempted_execution_count: usize,
|
||||||
|
@ -113,9 +119,8 @@ pub struct ExecuteAndCommitTransactionsOutput {
|
||||||
// to the block ending.
|
// to the block ending.
|
||||||
retryable_transaction_indexes: Vec<usize>,
|
retryable_transaction_indexes: Vec<usize>,
|
||||||
// A result that indicates whether transactions were successfully
|
// A result that indicates whether transactions were successfully
|
||||||
// committed into the Poh stream. If so, the result tells us
|
// committed into the Poh stream.
|
||||||
// how many such transactions were committed
|
commit_transactions_result: Result<Vec<CommitTransactionDetails>, PohRecorderError>,
|
||||||
commit_transactions_result: Result<Vec<bool>, PohRecorderError>,
|
|
||||||
execute_and_commit_timings: LeaderExecuteAndCommitTimings,
|
execute_and_commit_timings: LeaderExecuteAndCommitTimings,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1205,9 +1210,18 @@ impl BankingStage {
|
||||||
..
|
..
|
||||||
} = load_and_execute_transactions_output;
|
} = load_and_execute_transactions_output;
|
||||||
|
|
||||||
let mut transactions_execute_and_record_status: Vec<bool> = execution_results
|
let mut transactions_execute_and_record_status: Vec<_> = execution_results
|
||||||
.iter()
|
.iter()
|
||||||
.map(|execution_result| execution_result.was_executed())
|
.map(|execution_result| match execution_result {
|
||||||
|
TransactionExecutionResult::Executed(details) => {
|
||||||
|
CommitTransactionDetails::Committed {
|
||||||
|
compute_units: details.executed_units,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TransactionExecutionResult::NotExecuted { .. } => {
|
||||||
|
CommitTransactionDetails::NotCommitted
|
||||||
|
}
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let (freeze_lock, freeze_lock_time) =
|
let (freeze_lock, freeze_lock_time) =
|
||||||
|
@ -1237,7 +1251,7 @@ impl BankingStage {
|
||||||
|
|
||||||
// mark transactions that were executed but not recorded
|
// mark transactions that were executed but not recorded
|
||||||
retryable_record_transaction_indexes.iter().for_each(|i| {
|
retryable_record_transaction_indexes.iter().for_each(|i| {
|
||||||
transactions_execute_and_record_status[*i] = false;
|
transactions_execute_and_record_status[*i] = CommitTransactionDetails::NotCommitted;
|
||||||
});
|
});
|
||||||
|
|
||||||
inc_new_counter_info!(
|
inc_new_counter_info!(
|
||||||
|
@ -2168,6 +2182,7 @@ mod tests {
|
||||||
inner_instructions: None,
|
inner_instructions: None,
|
||||||
durable_nonce_fee: None,
|
durable_nonce_fee: None,
|
||||||
return_data: None,
|
return_data: None,
|
||||||
|
executed_units: 0u64,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
//! how transactions are included in blocks, and optimize those blocks.
|
//! how transactions are included in blocks, and optimize those blocks.
|
||||||
//!
|
//!
|
||||||
use {
|
use {
|
||||||
crate::banking_stage::BatchedTransactionDetails,
|
crate::banking_stage::{BatchedTransactionDetails, CommitTransactionDetails},
|
||||||
crossbeam_channel::{unbounded, Receiver, Sender},
|
crossbeam_channel::{unbounded, Receiver, Sender},
|
||||||
solana_measure::measure::Measure,
|
solana_measure::measure::Measure,
|
||||||
solana_runtime::{
|
solana_runtime::{
|
||||||
|
@ -177,7 +177,7 @@ impl QosService {
|
||||||
pub fn update_or_remove_transaction_costs<'a>(
|
pub fn update_or_remove_transaction_costs<'a>(
|
||||||
transaction_costs: impl Iterator<Item = &'a TransactionCost>,
|
transaction_costs: impl Iterator<Item = &'a TransactionCost>,
|
||||||
transaction_qos_results: impl Iterator<Item = &'a transaction::Result<()>>,
|
transaction_qos_results: impl Iterator<Item = &'a transaction::Result<()>>,
|
||||||
transaction_committed_status: Option<&Vec<bool>>,
|
transaction_committed_status: Option<&Vec<CommitTransactionDetails>>,
|
||||||
bank: &Arc<Bank>,
|
bank: &Arc<Bank>,
|
||||||
) {
|
) {
|
||||||
match transaction_committed_status {
|
match transaction_committed_status {
|
||||||
|
@ -196,20 +196,27 @@ impl QosService {
|
||||||
fn update_transaction_costs<'a>(
|
fn update_transaction_costs<'a>(
|
||||||
transaction_costs: impl Iterator<Item = &'a TransactionCost>,
|
transaction_costs: impl Iterator<Item = &'a TransactionCost>,
|
||||||
transaction_qos_results: impl Iterator<Item = &'a transaction::Result<()>>,
|
transaction_qos_results: impl Iterator<Item = &'a transaction::Result<()>>,
|
||||||
transaction_committed_status: &Vec<bool>,
|
transaction_committed_status: &Vec<CommitTransactionDetails>,
|
||||||
bank: &Arc<Bank>,
|
bank: &Arc<Bank>,
|
||||||
) {
|
) {
|
||||||
let mut cost_tracker = bank.write_cost_tracker().unwrap();
|
let mut cost_tracker = bank.write_cost_tracker().unwrap();
|
||||||
transaction_costs
|
transaction_costs
|
||||||
.zip(transaction_qos_results)
|
.zip(transaction_qos_results)
|
||||||
.zip(transaction_committed_status)
|
.zip(transaction_committed_status)
|
||||||
.for_each(|((tx_cost, qos_inclusion_result), was_committed)| {
|
.for_each(
|
||||||
|
|((tx_cost, qos_inclusion_result), transaction_committed_details)| {
|
||||||
// Only transactions that the qos service included have to be
|
// Only transactions that the qos service included have to be
|
||||||
// checked for update
|
// checked for update
|
||||||
if qos_inclusion_result.is_ok() && !*was_committed {
|
if qos_inclusion_result.is_ok() {
|
||||||
cost_tracker.remove(tx_cost);
|
match transaction_committed_details {
|
||||||
|
CommitTransactionDetails::Committed { compute_units } => {
|
||||||
|
cost_tracker.update_execution_cost(tx_cost, *compute_units)
|
||||||
}
|
}
|
||||||
});
|
CommitTransactionDetails::NotCommitted => cost_tracker.remove(tx_cost),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_transaction_costs<'a>(
|
fn remove_transaction_costs<'a>(
|
||||||
|
@ -661,19 +668,27 @@ mod tests {
|
||||||
let txs: Vec<SanitizedTransaction> = (0..transaction_count)
|
let txs: Vec<SanitizedTransaction> = (0..transaction_count)
|
||||||
.map(|_| transfer_tx.clone())
|
.map(|_| transfer_tx.clone())
|
||||||
.collect();
|
.collect();
|
||||||
|
let execute_units_adjustment = 10u64;
|
||||||
|
|
||||||
// assert all tx_costs should be applied to cost_tracker if all execution_results are all commited
|
// assert all tx_costs should be applied to cost_tracker if all execution_results are all committed
|
||||||
{
|
{
|
||||||
let qos_service = QosService::new(Arc::new(RwLock::new(CostModel::default())), 1);
|
let qos_service = QosService::new(Arc::new(RwLock::new(CostModel::default())), 1);
|
||||||
let txs_costs = qos_service.compute_transaction_costs(txs.iter());
|
let txs_costs = qos_service.compute_transaction_costs(txs.iter());
|
||||||
let total_txs_costs: u64 = txs_costs.iter().map(|cost| cost.sum()).sum();
|
let total_txs_cost: u64 = txs_costs.iter().map(|cost| cost.sum()).sum();
|
||||||
let (qos_results, _num_included) =
|
let (qos_results, _num_included) =
|
||||||
qos_service.select_transactions_per_cost(txs.iter(), txs_costs.iter(), &bank);
|
qos_service.select_transactions_per_cost(txs.iter(), txs_costs.iter(), &bank);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
total_txs_costs,
|
total_txs_cost,
|
||||||
bank.read_cost_tracker().unwrap().block_cost()
|
bank.read_cost_tracker().unwrap().block_cost()
|
||||||
);
|
);
|
||||||
let commited_status: Vec<bool> = (0..transaction_count).map(|_| true).collect();
|
// all transactions are committed with actual units more than estimated
|
||||||
|
let commited_status: Vec<CommitTransactionDetails> = txs_costs
|
||||||
|
.iter()
|
||||||
|
.map(|tx_cost| CommitTransactionDetails::Committed {
|
||||||
|
compute_units: tx_cost.bpf_execution_cost + execute_units_adjustment,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let final_txs_cost = total_txs_cost + execute_units_adjustment * transaction_count;
|
||||||
QosService::update_or_remove_transaction_costs(
|
QosService::update_or_remove_transaction_costs(
|
||||||
txs_costs.iter(),
|
txs_costs.iter(),
|
||||||
qos_results.iter(),
|
qos_results.iter(),
|
||||||
|
@ -681,7 +696,7 @@ mod tests {
|
||||||
&bank,
|
&bank,
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
total_txs_costs,
|
final_txs_cost,
|
||||||
bank.read_cost_tracker().unwrap().block_cost()
|
bank.read_cost_tracker().unwrap().block_cost()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -708,12 +723,17 @@ mod tests {
|
||||||
.map(|_| transfer_tx.clone())
|
.map(|_| transfer_tx.clone())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// assert all tx_costs should be removed from cost_tracker if all execution_results are all NotExecuted
|
// assert all tx_costs should be removed from cost_tracker if all execution_results are all Not Committed
|
||||||
{
|
{
|
||||||
let qos_service = QosService::new(Arc::new(RwLock::new(CostModel::default())), 1);
|
let qos_service = QosService::new(Arc::new(RwLock::new(CostModel::default())), 1);
|
||||||
let txs_costs = qos_service.compute_transaction_costs(txs.iter());
|
let txs_costs = qos_service.compute_transaction_costs(txs.iter());
|
||||||
|
let total_txs_cost: u64 = txs_costs.iter().map(|cost| cost.sum()).sum();
|
||||||
let (qos_results, _num_included) =
|
let (qos_results, _num_included) =
|
||||||
qos_service.select_transactions_per_cost(txs.iter(), txs_costs.iter(), &bank);
|
qos_service.select_transactions_per_cost(txs.iter(), txs_costs.iter(), &bank);
|
||||||
|
assert_eq!(
|
||||||
|
total_txs_cost,
|
||||||
|
bank.read_cost_tracker().unwrap().block_cost()
|
||||||
|
);
|
||||||
QosService::update_or_remove_transaction_costs(
|
QosService::update_or_remove_transaction_costs(
|
||||||
txs_costs.iter(),
|
txs_costs.iter(),
|
||||||
qos_results.iter(),
|
qos_results.iter(),
|
||||||
|
@ -741,31 +761,55 @@ mod tests {
|
||||||
let txs: Vec<SanitizedTransaction> = (0..transaction_count)
|
let txs: Vec<SanitizedTransaction> = (0..transaction_count)
|
||||||
.map(|_| transfer_tx.clone())
|
.map(|_| transfer_tx.clone())
|
||||||
.collect();
|
.collect();
|
||||||
|
let execute_units_adjustment = 10u64;
|
||||||
|
|
||||||
// assert only commited tx_costs are applied cost_tracker
|
// assert only commited tx_costs are applied cost_tracker
|
||||||
{
|
{
|
||||||
let qos_service = QosService::new(Arc::new(RwLock::new(CostModel::default())), 1);
|
let qos_service = QosService::new(Arc::new(RwLock::new(CostModel::default())), 1);
|
||||||
let txs_costs = qos_service.compute_transaction_costs(txs.iter());
|
let txs_costs = qos_service.compute_transaction_costs(txs.iter());
|
||||||
|
let total_txs_cost: u64 = txs_costs.iter().map(|cost| cost.sum()).sum();
|
||||||
let (qos_results, _num_included) =
|
let (qos_results, _num_included) =
|
||||||
qos_service.select_transactions_per_cost(txs.iter(), txs_costs.iter(), &bank);
|
qos_service.select_transactions_per_cost(txs.iter(), txs_costs.iter(), &bank);
|
||||||
let commited_status: Vec<bool> = (0..transaction_count).map(|n| n != 0).collect();
|
assert_eq!(
|
||||||
|
total_txs_cost,
|
||||||
|
bank.read_cost_tracker().unwrap().block_cost()
|
||||||
|
);
|
||||||
|
// Half of transactions are not committed, the rest with cost adjustment
|
||||||
|
let commited_status: Vec<CommitTransactionDetails> = txs_costs
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(n, tx_cost)| {
|
||||||
|
if n % 2 == 0 {
|
||||||
|
CommitTransactionDetails::NotCommitted
|
||||||
|
} else {
|
||||||
|
CommitTransactionDetails::Committed {
|
||||||
|
compute_units: tx_cost.bpf_execution_cost + execute_units_adjustment,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
QosService::update_or_remove_transaction_costs(
|
QosService::update_or_remove_transaction_costs(
|
||||||
txs_costs.iter(),
|
txs_costs.iter(),
|
||||||
qos_results.iter(),
|
qos_results.iter(),
|
||||||
Some(&commited_status),
|
Some(&commited_status),
|
||||||
&bank,
|
&bank,
|
||||||
);
|
);
|
||||||
let expected_committed_units: u64 = txs_costs
|
|
||||||
.iter()
|
// assert the final block cost
|
||||||
.enumerate()
|
let mut expected_final_txs_count = 0u64;
|
||||||
.map(|(n, cost)| if n < 1 { 0 } else { cost.sum() })
|
let mut expected_final_block_cost = 0u64;
|
||||||
.sum();
|
txs_costs.iter().enumerate().for_each(|(n, cost)| {
|
||||||
|
if n % 2 != 0 {
|
||||||
|
expected_final_txs_count += 1;
|
||||||
|
expected_final_block_cost += cost.sum() + execute_units_adjustment;
|
||||||
|
}
|
||||||
|
});
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
expected_committed_units,
|
expected_final_block_cost,
|
||||||
bank.read_cost_tracker().unwrap().block_cost()
|
bank.read_cost_tracker().unwrap().block_cost()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
transaction_count - 1,
|
expected_final_txs_count,
|
||||||
bank.read_cost_tracker().unwrap().transaction_count()
|
bank.read_cost_tracker().unwrap().transaction_count()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,6 +102,7 @@ impl TransactionStatusService {
|
||||||
inner_instructions,
|
inner_instructions,
|
||||||
durable_nonce_fee,
|
durable_nonce_fee,
|
||||||
return_data,
|
return_data,
|
||||||
|
..
|
||||||
} = details;
|
} = details;
|
||||||
let lamports_per_signature = match durable_nonce_fee {
|
let lamports_per_signature = match durable_nonce_fee {
|
||||||
Some(DurableNonceFee::Valid(lamports_per_signature)) => {
|
Some(DurableNonceFee::Valid(lamports_per_signature)) => {
|
||||||
|
@ -352,6 +353,7 @@ pub(crate) mod tests {
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
)),
|
)),
|
||||||
return_data: None,
|
return_data: None,
|
||||||
|
executed_units: 0u64,
|
||||||
});
|
});
|
||||||
|
|
||||||
let balances = TransactionBalancesSet {
|
let balances = TransactionBalancesSet {
|
||||||
|
|
|
@ -1398,6 +1398,7 @@ mod tests {
|
||||||
inner_instructions: None,
|
inner_instructions: None,
|
||||||
durable_nonce_fee: nonce.map(DurableNonceFee::from),
|
durable_nonce_fee: nonce.map(DurableNonceFee::from),
|
||||||
return_data: None,
|
return_data: None,
|
||||||
|
executed_units: 0u64,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -597,6 +597,7 @@ pub struct TransactionExecutionDetails {
|
||||||
pub inner_instructions: Option<InnerInstructionsList>,
|
pub inner_instructions: Option<InnerInstructionsList>,
|
||||||
pub durable_nonce_fee: Option<DurableNonceFee>,
|
pub durable_nonce_fee: Option<DurableNonceFee>,
|
||||||
pub return_data: Option<TransactionReturnData>,
|
pub return_data: Option<TransactionReturnData>,
|
||||||
|
pub executed_units: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Type safe representation of a transaction execution attempt which
|
/// Type safe representation of a transaction execution attempt which
|
||||||
|
@ -3964,6 +3965,8 @@ impl Bank {
|
||||||
|
|
||||||
let (blockhash, lamports_per_signature) = self.last_blockhash_and_lamports_per_signature();
|
let (blockhash, lamports_per_signature) = self.last_blockhash_and_lamports_per_signature();
|
||||||
|
|
||||||
|
let mut executed_units = 0u64;
|
||||||
|
|
||||||
let mut process_message_time = Measure::start("process_message_time");
|
let mut process_message_time = Measure::start("process_message_time");
|
||||||
let process_result = MessageProcessor::process_message(
|
let process_result = MessageProcessor::process_message(
|
||||||
&self.builtin_programs.vec,
|
&self.builtin_programs.vec,
|
||||||
|
@ -3980,8 +3983,10 @@ impl Bank {
|
||||||
blockhash,
|
blockhash,
|
||||||
lamports_per_signature,
|
lamports_per_signature,
|
||||||
self.load_accounts_data_len(),
|
self.load_accounts_data_len(),
|
||||||
|
&mut executed_units,
|
||||||
);
|
);
|
||||||
process_message_time.stop();
|
process_message_time.stop();
|
||||||
|
|
||||||
saturating_add_assign!(
|
saturating_add_assign!(
|
||||||
timings.execute_accessories.process_message_us,
|
timings.execute_accessories.process_message_us,
|
||||||
process_message_time.as_us()
|
process_message_time.as_us()
|
||||||
|
@ -4062,6 +4067,7 @@ impl Bank {
|
||||||
inner_instructions,
|
inner_instructions,
|
||||||
durable_nonce_fee,
|
durable_nonce_fee,
|
||||||
return_data,
|
return_data,
|
||||||
|
executed_units,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6991,6 +6997,7 @@ pub(crate) mod tests {
|
||||||
inner_instructions: None,
|
inner_instructions: None,
|
||||||
durable_nonce_fee: nonce.map(DurableNonceFee::from),
|
durable_nonce_fee: nonce.map(DurableNonceFee::from),
|
||||||
return_data: None,
|
return_data: None,
|
||||||
|
executed_units: 0u64,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
//!
|
//!
|
||||||
use {
|
use {
|
||||||
crate::{block_cost_limits::*, cost_model::TransactionCost},
|
crate::{block_cost_limits::*, cost_model::TransactionCost},
|
||||||
solana_sdk::{clock::Slot, pubkey::Pubkey},
|
solana_sdk::{clock::Slot, pubkey::Pubkey, saturating_add_assign},
|
||||||
std::collections::HashMap,
|
std::{cmp::Ordering, collections::HashMap},
|
||||||
};
|
};
|
||||||
|
|
||||||
const WRITABLE_ACCOUNTS_PER_BLOCK: usize = 512;
|
const WRITABLE_ACCOUNTS_PER_BLOCK: usize = 512;
|
||||||
|
@ -95,6 +95,29 @@ impl CostTracker {
|
||||||
Ok(self.block_cost)
|
Ok(self.block_cost)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_execution_cost(
|
||||||
|
&mut self,
|
||||||
|
estimated_tx_cost: &TransactionCost,
|
||||||
|
actual_execution_units: u64,
|
||||||
|
) {
|
||||||
|
let estimated_execution_units = estimated_tx_cost.bpf_execution_cost;
|
||||||
|
match actual_execution_units.cmp(&estimated_execution_units) {
|
||||||
|
Ordering::Equal => (),
|
||||||
|
Ordering::Greater => {
|
||||||
|
self.add_transaction_execution_cost(
|
||||||
|
estimated_tx_cost,
|
||||||
|
actual_execution_units - estimated_execution_units,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ordering::Less => {
|
||||||
|
self.sub_transaction_execution_cost(
|
||||||
|
estimated_tx_cost,
|
||||||
|
estimated_execution_units - actual_execution_units,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn remove(&mut self, tx_cost: &TransactionCost) {
|
pub fn remove(&mut self, tx_cost: &TransactionCost) {
|
||||||
self.remove_transaction_cost(tx_cost);
|
self.remove_transaction_cost(tx_cost);
|
||||||
}
|
}
|
||||||
|
@ -121,11 +144,7 @@ impl CostTracker {
|
||||||
("block_cost", self.block_cost as i64, i64),
|
("block_cost", self.block_cost as i64, i64),
|
||||||
("vote_cost", self.vote_cost as i64, i64),
|
("vote_cost", self.vote_cost as i64, i64),
|
||||||
("transaction_count", self.transaction_count as i64, i64),
|
("transaction_count", self.transaction_count as i64, i64),
|
||||||
(
|
("number_of_accounts", self.number_of_accounts() as i64, i64),
|
||||||
"number_of_accounts",
|
|
||||||
self.cost_by_writable_accounts.len() as i64,
|
|
||||||
i64
|
|
||||||
),
|
|
||||||
("costliest_account", costliest_account.to_string(), String),
|
("costliest_account", costliest_account.to_string(), String),
|
||||||
("costliest_account_cost", costliest_account_cost as i64, i64),
|
("costliest_account_cost", costliest_account_cost as i64, i64),
|
||||||
("account_data_size", self.account_data_size, i64),
|
("account_data_size", self.account_data_size, i64),
|
||||||
|
@ -199,40 +218,56 @@ impl CostTracker {
|
||||||
|
|
||||||
fn add_transaction_cost(&mut self, tx_cost: &TransactionCost) {
|
fn add_transaction_cost(&mut self, tx_cost: &TransactionCost) {
|
||||||
let cost = tx_cost.sum();
|
let cost = tx_cost.sum();
|
||||||
for account_key in tx_cost.writable_accounts.iter() {
|
self.add_transaction_execution_cost(tx_cost, cost);
|
||||||
let account_cost = self
|
saturating_add_assign!(self.account_data_size, tx_cost.account_data_size);
|
||||||
.cost_by_writable_accounts
|
saturating_add_assign!(self.transaction_count, 1);
|
||||||
.entry(*account_key)
|
|
||||||
.or_insert(0);
|
|
||||||
*account_cost = account_cost.saturating_add(cost);
|
|
||||||
}
|
|
||||||
self.block_cost = self.block_cost.saturating_add(cost);
|
|
||||||
if tx_cost.is_simple_vote {
|
|
||||||
self.vote_cost = self.vote_cost.saturating_add(cost);
|
|
||||||
}
|
|
||||||
self.account_data_size = self
|
|
||||||
.account_data_size
|
|
||||||
.saturating_add(tx_cost.account_data_size);
|
|
||||||
self.transaction_count = self.transaction_count.saturating_add(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_transaction_cost(&mut self, tx_cost: &TransactionCost) {
|
fn remove_transaction_cost(&mut self, tx_cost: &TransactionCost) {
|
||||||
let cost = tx_cost.sum();
|
let cost = tx_cost.sum();
|
||||||
|
self.sub_transaction_execution_cost(tx_cost, cost);
|
||||||
|
self.account_data_size = self
|
||||||
|
.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
|
let account_cost = self
|
||||||
.cost_by_writable_accounts
|
.cost_by_writable_accounts
|
||||||
.entry(*account_key)
|
.entry(*account_key)
|
||||||
.or_insert(0);
|
.or_insert(0);
|
||||||
*account_cost = account_cost.saturating_sub(cost);
|
*account_cost = account_cost.saturating_add(adjustment);
|
||||||
}
|
}
|
||||||
self.block_cost = self.block_cost.saturating_sub(cost);
|
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_sub(cost);
|
self.vote_cost = self.vote_cost.saturating_add(adjustment);
|
||||||
}
|
}
|
||||||
self.account_data_size = self
|
}
|
||||||
.account_data_size
|
|
||||||
.saturating_sub(tx_cost.account_data_size);
|
/// Substract extra execution units from cost_tracker
|
||||||
self.transaction_count = self.transaction_count.saturating_sub(1);
|
fn sub_transaction_execution_cost(&mut self, tx_cost: &TransactionCost, adjustment: u64) {
|
||||||
|
for account_key in tx_cost.writable_accounts.iter() {
|
||||||
|
let account_cost = self
|
||||||
|
.cost_by_writable_accounts
|
||||||
|
.entry(*account_key)
|
||||||
|
.or_insert(0);
|
||||||
|
*account_cost = account_cost.saturating_sub(adjustment);
|
||||||
|
}
|
||||||
|
self.block_cost = self.block_cost.saturating_sub(adjustment);
|
||||||
|
if tx_cost.is_simple_vote {
|
||||||
|
self.vote_cost = self.vote_cost.saturating_sub(adjustment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// count number of none-zero CU accounts
|
||||||
|
fn number_of_accounts(&self) -> usize {
|
||||||
|
self.cost_by_writable_accounts
|
||||||
|
.iter()
|
||||||
|
.map(|(_key, units)| if *units > 0 { 1 } else { 0 })
|
||||||
|
.sum()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -676,4 +711,171 @@ mod tests {
|
||||||
assert_eq!(acct2, costliest_account);
|
assert_eq!(acct2, costliest_account);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_adjust_transaction_execution_cost() {
|
||||||
|
let acct1 = Pubkey::new_unique();
|
||||||
|
let acct2 = Pubkey::new_unique();
|
||||||
|
let acct3 = Pubkey::new_unique();
|
||||||
|
let cost = 100;
|
||||||
|
let account_max = cost * 2;
|
||||||
|
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 {
|
||||||
|
writable_accounts: vec![acct1, acct2, acct3],
|
||||||
|
bpf_execution_cost: cost,
|
||||||
|
..TransactionCost::default()
|
||||||
|
};
|
||||||
|
let mut expected_block_cost = tx_cost.sum();
|
||||||
|
let expected_tx_count = 1;
|
||||||
|
assert!(testee.try_add(&tx_cost).is_ok());
|
||||||
|
assert_eq!(expected_block_cost, testee.block_cost());
|
||||||
|
assert_eq!(expected_tx_count, testee.transaction_count());
|
||||||
|
testee
|
||||||
|
.cost_by_writable_accounts
|
||||||
|
.iter()
|
||||||
|
.for_each(|(_key, units)| {
|
||||||
|
assert_eq!(expected_block_cost, *units);
|
||||||
|
});
|
||||||
|
|
||||||
|
// adjust up
|
||||||
|
{
|
||||||
|
let adjustment = 50u64;
|
||||||
|
testee.add_transaction_execution_cost(&tx_cost, adjustment);
|
||||||
|
expected_block_cost += 50;
|
||||||
|
assert_eq!(expected_block_cost, testee.block_cost());
|
||||||
|
assert_eq!(expected_tx_count, testee.transaction_count());
|
||||||
|
testee
|
||||||
|
.cost_by_writable_accounts
|
||||||
|
.iter()
|
||||||
|
.for_each(|(_key, units)| {
|
||||||
|
assert_eq!(expected_block_cost, *units);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// adjust down
|
||||||
|
{
|
||||||
|
let adjustment = 50u64;
|
||||||
|
testee.sub_transaction_execution_cost(&tx_cost, adjustment);
|
||||||
|
expected_block_cost -= 50;
|
||||||
|
assert_eq!(expected_block_cost, testee.block_cost());
|
||||||
|
assert_eq!(expected_tx_count, testee.transaction_count());
|
||||||
|
testee
|
||||||
|
.cost_by_writable_accounts
|
||||||
|
.iter()
|
||||||
|
.for_each(|(_key, units)| {
|
||||||
|
assert_eq!(expected_block_cost, *units);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// adjust overflow
|
||||||
|
{
|
||||||
|
testee.add_transaction_execution_cost(&tx_cost, u64::MAX);
|
||||||
|
// expect block cost set to limit
|
||||||
|
assert_eq!(u64::MAX, testee.block_cost());
|
||||||
|
assert_eq!(expected_tx_count, testee.transaction_count());
|
||||||
|
testee
|
||||||
|
.cost_by_writable_accounts
|
||||||
|
.iter()
|
||||||
|
.for_each(|(_key, units)| {
|
||||||
|
assert_eq!(u64::MAX, *units);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// adjust underflow
|
||||||
|
{
|
||||||
|
testee.sub_transaction_execution_cost(&tx_cost, u64::MAX);
|
||||||
|
// expect block cost set to limit
|
||||||
|
assert_eq!(u64::MIN, testee.block_cost());
|
||||||
|
assert_eq!(expected_tx_count, testee.transaction_count());
|
||||||
|
testee
|
||||||
|
.cost_by_writable_accounts
|
||||||
|
.iter()
|
||||||
|
.for_each(|(_key, units)| {
|
||||||
|
assert_eq!(u64::MIN, *units);
|
||||||
|
});
|
||||||
|
// assert the number of non-empty accounts is zero, but map
|
||||||
|
// still contains 3 account
|
||||||
|
assert_eq!(0, testee.number_of_accounts());
|
||||||
|
assert_eq!(3, testee.cost_by_writable_accounts.len());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_update_execution_cost() {
|
||||||
|
let acct1 = Pubkey::new_unique();
|
||||||
|
let acct2 = Pubkey::new_unique();
|
||||||
|
let acct3 = Pubkey::new_unique();
|
||||||
|
let cost = 100;
|
||||||
|
|
||||||
|
let tx_cost = TransactionCost {
|
||||||
|
writable_accounts: vec![acct1, acct2, acct3],
|
||||||
|
bpf_execution_cost: cost,
|
||||||
|
..TransactionCost::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut cost_tracker = CostTracker::default();
|
||||||
|
|
||||||
|
// Assert OK to add tx_cost
|
||||||
|
assert!(cost_tracker.try_add(&tx_cost).is_ok());
|
||||||
|
let (_costliest_account, costliest_account_cost) = cost_tracker.find_costliest_account();
|
||||||
|
assert_eq!(cost, cost_tracker.block_cost);
|
||||||
|
assert_eq!(cost, costliest_account_cost);
|
||||||
|
assert_eq!(1, cost_tracker.transaction_count);
|
||||||
|
|
||||||
|
// assert no-change if actual units is same as estimated units
|
||||||
|
let mut expected_cost = cost;
|
||||||
|
cost_tracker.update_execution_cost(&tx_cost, cost);
|
||||||
|
let (_costliest_account, costliest_account_cost) = cost_tracker.find_costliest_account();
|
||||||
|
assert_eq!(expected_cost, cost_tracker.block_cost);
|
||||||
|
assert_eq!(expected_cost, costliest_account_cost);
|
||||||
|
assert_eq!(1, cost_tracker.transaction_count);
|
||||||
|
|
||||||
|
// assert cost are adjusted down
|
||||||
|
let reduced_units = 3;
|
||||||
|
expected_cost -= reduced_units;
|
||||||
|
cost_tracker.update_execution_cost(&tx_cost, cost - reduced_units);
|
||||||
|
let (_costliest_account, costliest_account_cost) = cost_tracker.find_costliest_account();
|
||||||
|
assert_eq!(expected_cost, cost_tracker.block_cost);
|
||||||
|
assert_eq!(expected_cost, costliest_account_cost);
|
||||||
|
assert_eq!(1, cost_tracker.transaction_count);
|
||||||
|
|
||||||
|
// assert cost are adjusted up
|
||||||
|
let increased_units = 1;
|
||||||
|
expected_cost += increased_units;
|
||||||
|
cost_tracker.update_execution_cost(&tx_cost, cost + increased_units);
|
||||||
|
let (_costliest_account, costliest_account_cost) = cost_tracker.find_costliest_account();
|
||||||
|
assert_eq!(expected_cost, cost_tracker.block_cost);
|
||||||
|
assert_eq!(expected_cost, costliest_account_cost);
|
||||||
|
assert_eq!(1, cost_tracker.transaction_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_remove_transaction_cost() {
|
||||||
|
let mut cost_tracker = CostTracker::default();
|
||||||
|
|
||||||
|
let cost = 100u64;
|
||||||
|
let tx_cost = TransactionCost {
|
||||||
|
writable_accounts: vec![Pubkey::new_unique()],
|
||||||
|
bpf_execution_cost: cost,
|
||||||
|
..TransactionCost::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
cost_tracker.add_transaction_cost(&tx_cost);
|
||||||
|
// assert cost_tracker is reverted to default
|
||||||
|
assert_eq!(1, cost_tracker.transaction_count);
|
||||||
|
assert_eq!(1, cost_tracker.number_of_accounts());
|
||||||
|
assert_eq!(cost, cost_tracker.block_cost);
|
||||||
|
assert_eq!(0, cost_tracker.vote_cost);
|
||||||
|
assert_eq!(0, cost_tracker.account_data_size);
|
||||||
|
|
||||||
|
cost_tracker.remove_transaction_cost(&tx_cost);
|
||||||
|
// assert cost_tracker is reverted to default
|
||||||
|
assert_eq!(0, cost_tracker.transaction_count);
|
||||||
|
assert_eq!(0, cost_tracker.number_of_accounts());
|
||||||
|
assert_eq!(0, cost_tracker.block_cost);
|
||||||
|
assert_eq!(0, cost_tracker.vote_cost);
|
||||||
|
assert_eq!(0, cost_tracker.account_data_size);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,6 +64,7 @@ impl MessageProcessor {
|
||||||
blockhash: Hash,
|
blockhash: Hash,
|
||||||
lamports_per_signature: u64,
|
lamports_per_signature: u64,
|
||||||
current_accounts_data_len: u64,
|
current_accounts_data_len: u64,
|
||||||
|
accumulated_consumed_units: &mut u64,
|
||||||
) -> Result<ProcessedMessageInfo, TransactionError> {
|
) -> Result<ProcessedMessageInfo, TransactionError> {
|
||||||
let mut invoke_context = InvokeContext::new(
|
let mut invoke_context = InvokeContext::new(
|
||||||
transaction_context,
|
transaction_context,
|
||||||
|
@ -134,6 +135,8 @@ impl MessageProcessor {
|
||||||
timings,
|
timings,
|
||||||
);
|
);
|
||||||
time.stop();
|
time.stop();
|
||||||
|
*accumulated_consumed_units =
|
||||||
|
accumulated_consumed_units.saturating_add(compute_units_consumed);
|
||||||
timings.details.accumulate_program(
|
timings.details.accumulate_program(
|
||||||
program_id,
|
program_id,
|
||||||
time.as_us(),
|
time.as_us(),
|
||||||
|
@ -282,6 +285,7 @@ mod tests {
|
||||||
Hash::default(),
|
Hash::default(),
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
&mut 0,
|
||||||
);
|
);
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -330,6 +334,7 @@ mod tests {
|
||||||
Hash::default(),
|
Hash::default(),
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
&mut 0,
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result,
|
result,
|
||||||
|
@ -368,6 +373,7 @@ mod tests {
|
||||||
Hash::default(),
|
Hash::default(),
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
&mut 0,
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result,
|
result,
|
||||||
|
@ -503,6 +509,7 @@ mod tests {
|
||||||
Hash::default(),
|
Hash::default(),
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
&mut 0,
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result,
|
result,
|
||||||
|
@ -536,6 +543,7 @@ mod tests {
|
||||||
Hash::default(),
|
Hash::default(),
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
&mut 0,
|
||||||
);
|
);
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
|
|
||||||
|
@ -566,6 +574,7 @@ mod tests {
|
||||||
Hash::default(),
|
Hash::default(),
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
&mut 0,
|
||||||
);
|
);
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -644,7 +653,9 @@ mod tests {
|
||||||
Hash::default(),
|
Hash::default(),
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
&mut 0,
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result,
|
result,
|
||||||
Err(TransactionError::InstructionError(
|
Err(TransactionError::InstructionError(
|
||||||
|
|
Loading…
Reference in New Issue