Cost model tracks builtins and bpf programs separately (#24468)
* Cost model tracks builtins and bpf programs separatele (enables adjusting block cost by actual bpf programs execution costs) * Address reviews: expand test; add metrics stat
This commit is contained in:
parent
3bbfaae7b6
commit
94b0186a96
|
@ -334,7 +334,8 @@ pub struct BatchedTransactionCostDetails {
|
|||
pub batched_signature_cost: u64,
|
||||
pub batched_write_lock_cost: u64,
|
||||
pub batched_data_bytes_cost: u64,
|
||||
pub batched_execute_cost: u64,
|
||||
pub batched_builtins_execute_cost: u64,
|
||||
pub batched_bpf_execute_cost: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
|
@ -1461,8 +1462,14 @@ impl BankingStage {
|
|||
cost.data_bytes_cost
|
||||
);
|
||||
saturating_add_assign!(
|
||||
batched_transaction_details.costs.batched_execute_cost,
|
||||
cost.execution_cost
|
||||
batched_transaction_details
|
||||
.costs
|
||||
.batched_builtins_execute_cost,
|
||||
cost.builtins_execution_cost
|
||||
);
|
||||
saturating_add_assign!(
|
||||
batched_transaction_details.costs.batched_bpf_execute_cost,
|
||||
cost.bpf_execution_cost
|
||||
);
|
||||
}
|
||||
Err(transaction_error) => match transaction_error {
|
||||
|
@ -4362,39 +4369,38 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_accumulate_batched_transaction_costs() {
|
||||
let tx_costs = vec![
|
||||
TransactionCost {
|
||||
signature_cost: 1,
|
||||
write_lock_cost: 2,
|
||||
data_bytes_cost: 3,
|
||||
execution_cost: 10,
|
||||
let signature_cost = 1;
|
||||
let write_lock_cost = 2;
|
||||
let data_bytes_cost = 3;
|
||||
let builtins_execution_cost = 4;
|
||||
let bpf_execution_cost = 10;
|
||||
let num_txs = 4;
|
||||
|
||||
let tx_costs: Vec<_> = (0..num_txs)
|
||||
.map(|_| TransactionCost {
|
||||
signature_cost,
|
||||
write_lock_cost,
|
||||
data_bytes_cost,
|
||||
builtins_execution_cost,
|
||||
bpf_execution_cost,
|
||||
..TransactionCost::default()
|
||||
},
|
||||
TransactionCost {
|
||||
signature_cost: 4,
|
||||
write_lock_cost: 5,
|
||||
data_bytes_cost: 6,
|
||||
execution_cost: 20,
|
||||
..TransactionCost::default()
|
||||
},
|
||||
TransactionCost {
|
||||
signature_cost: 7,
|
||||
write_lock_cost: 8,
|
||||
data_bytes_cost: 9,
|
||||
execution_cost: 40,
|
||||
..TransactionCost::default()
|
||||
},
|
||||
];
|
||||
let tx_results = vec![
|
||||
Ok(()),
|
||||
Ok(()),
|
||||
Err(TransactionError::WouldExceedMaxBlockCostLimit),
|
||||
];
|
||||
// should only accumulate first two cost that are OK
|
||||
let expected_signatures = 5;
|
||||
let expected_write_locks = 7;
|
||||
let expected_data_bytes = 9;
|
||||
let expected_executions = 30;
|
||||
})
|
||||
.collect();
|
||||
let tx_results: Vec<_> = (0..num_txs)
|
||||
.map(|n| {
|
||||
if n % 2 == 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(TransactionError::WouldExceedMaxBlockCostLimit)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
// should only accumulate half of the costs that are OK
|
||||
let expected_signatures = signature_cost * (num_txs / 2);
|
||||
let expected_write_locks = write_lock_cost * (num_txs / 2);
|
||||
let expected_data_bytes = data_bytes_cost * (num_txs / 2);
|
||||
let expected_builtins_execution_costs = builtins_execution_cost * (num_txs / 2);
|
||||
let expected_bpf_execution_costs = bpf_execution_cost * (num_txs / 2);
|
||||
let batched_transaction_details =
|
||||
BankingStage::accumulate_batched_transaction_costs(tx_costs.iter(), tx_results.iter());
|
||||
assert_eq!(
|
||||
|
@ -4410,8 +4416,14 @@ mod tests {
|
|||
batched_transaction_details.costs.batched_data_bytes_cost
|
||||
);
|
||||
assert_eq!(
|
||||
expected_executions,
|
||||
batched_transaction_details.costs.batched_execute_cost
|
||||
expected_builtins_execution_costs,
|
||||
batched_transaction_details
|
||||
.costs
|
||||
.batched_builtins_execute_cost
|
||||
);
|
||||
assert_eq!(
|
||||
expected_bpf_execution_costs,
|
||||
batched_transaction_details.costs.batched_bpf_execute_cost
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -252,8 +252,14 @@ impl QosService {
|
|||
batched_transaction_details.costs.batched_data_bytes_cost,
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
self.metrics.stats.estimated_execute_cu.fetch_add(
|
||||
batched_transaction_details.costs.batched_execute_cost,
|
||||
self.metrics.stats.estimated_builtins_execute_cu.fetch_add(
|
||||
batched_transaction_details
|
||||
.costs
|
||||
.batched_builtins_execute_cost,
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
self.metrics.stats.estimated_bpf_execute_cu.fetch_add(
|
||||
batched_transaction_details.costs.batched_bpf_execute_cost,
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
|
||||
|
@ -307,7 +313,7 @@ impl QosService {
|
|||
pub fn accumulate_actual_execute_cu(&self, units: u64) {
|
||||
self.metrics
|
||||
.stats
|
||||
.actual_execute_cu
|
||||
.actual_bpf_execute_cu
|
||||
.fetch_add(units, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
|
@ -376,11 +382,14 @@ struct QosServiceMetricsStats {
|
|||
/// accumulated estimated instructino data Compute Units to be packed into block
|
||||
estimated_data_bytes_cu: AtomicU64,
|
||||
|
||||
/// accumulated estimated program Compute Units to be packed into block
|
||||
estimated_execute_cu: AtomicU64,
|
||||
/// accumulated estimated builtin programs Compute Units to be packed into block
|
||||
estimated_builtins_execute_cu: AtomicU64,
|
||||
|
||||
/// accumulated estimated BPF program Compute Units to be packed into block
|
||||
estimated_bpf_execute_cu: AtomicU64,
|
||||
|
||||
/// accumulated actual program Compute Units that have been packed into block
|
||||
actual_execute_cu: AtomicU64,
|
||||
actual_bpf_execute_cu: AtomicU64,
|
||||
|
||||
/// accumulated actual program execute micro-sec that have been packed into block
|
||||
actual_execute_time_us: AtomicU64,
|
||||
|
@ -461,13 +470,22 @@ impl QosServiceMetrics {
|
|||
i64
|
||||
),
|
||||
(
|
||||
"estimated_execute_cu",
|
||||
self.stats.estimated_execute_cu.swap(0, Ordering::Relaxed) as i64,
|
||||
"estimated_builtins_execute_cu",
|
||||
self.stats
|
||||
.estimated_builtins_execute_cu
|
||||
.swap(0, Ordering::Relaxed) as i64,
|
||||
i64
|
||||
),
|
||||
(
|
||||
"actual_execute_cu",
|
||||
self.stats.actual_execute_cu.swap(0, Ordering::Relaxed) as i64,
|
||||
"estimated_bpf_execute_cu",
|
||||
self.stats
|
||||
.estimated_bpf_execute_cu
|
||||
.swap(0, Ordering::Relaxed) as i64,
|
||||
i64
|
||||
),
|
||||
(
|
||||
"actual_bpf_execute_cu",
|
||||
self.stats.actual_bpf_execute_cu.swap(0, Ordering::Relaxed) as i64,
|
||||
i64
|
||||
),
|
||||
(
|
||||
|
|
|
@ -394,6 +394,7 @@ fn execute_transactions(
|
|||
inner_instructions,
|
||||
durable_nonce_fee,
|
||||
return_data,
|
||||
..
|
||||
} = details;
|
||||
|
||||
let lamports_per_signature = match durable_nonce_fee {
|
||||
|
|
|
@ -22,7 +22,8 @@ pub struct TransactionCost {
|
|||
pub signature_cost: u64,
|
||||
pub write_lock_cost: u64,
|
||||
pub data_bytes_cost: u64,
|
||||
pub execution_cost: u64,
|
||||
pub builtins_execution_cost: u64,
|
||||
pub bpf_execution_cost: u64,
|
||||
pub account_data_size: u64,
|
||||
pub is_simple_vote: bool,
|
||||
}
|
||||
|
@ -34,7 +35,8 @@ impl Default for TransactionCost {
|
|||
signature_cost: 0u64,
|
||||
write_lock_cost: 0u64,
|
||||
data_bytes_cost: 0u64,
|
||||
execution_cost: 0u64,
|
||||
builtins_execution_cost: 0u64,
|
||||
bpf_execution_cost: 0u64,
|
||||
account_data_size: 0u64,
|
||||
is_simple_vote: false,
|
||||
}
|
||||
|
@ -54,12 +56,17 @@ impl TransactionCost {
|
|||
self.signature_cost = 0;
|
||||
self.write_lock_cost = 0;
|
||||
self.data_bytes_cost = 0;
|
||||
self.execution_cost = 0;
|
||||
self.builtins_execution_cost = 0;
|
||||
self.bpf_execution_cost = 0;
|
||||
self.is_simple_vote = false;
|
||||
}
|
||||
|
||||
pub fn sum(&self) -> u64 {
|
||||
self.signature_cost + self.write_lock_cost + self.data_bytes_cost + self.execution_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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,7 +86,6 @@ impl CostModel {
|
|||
cost_table
|
||||
.iter()
|
||||
.map(|(key, cost)| (key, cost))
|
||||
.chain(BUILT_IN_INSTRUCTION_COSTS.iter())
|
||||
.for_each(|(program_id, cost)| {
|
||||
self.upsert_instruction_cost(program_id, *cost);
|
||||
});
|
||||
|
@ -91,7 +97,8 @@ impl CostModel {
|
|||
tx_cost.signature_cost = self.get_signature_cost(transaction);
|
||||
self.get_write_lock_cost(&mut tx_cost, transaction);
|
||||
tx_cost.data_bytes_cost = self.get_data_bytes_cost(transaction);
|
||||
tx_cost.execution_cost = self.get_transaction_cost(transaction);
|
||||
(tx_cost.builtins_execution_cost, tx_cost.bpf_execution_cost) =
|
||||
self.get_transaction_cost(transaction);
|
||||
tx_cost.account_data_size = self.calculate_account_data_size(transaction);
|
||||
tx_cost.is_simple_vote = transaction.is_simple_vote_transaction();
|
||||
|
||||
|
@ -153,19 +160,25 @@ impl CostModel {
|
|||
data_bytes_cost
|
||||
}
|
||||
|
||||
fn get_transaction_cost(&self, transaction: &SanitizedTransaction) -> u64 {
|
||||
let mut cost: u64 = 0;
|
||||
fn get_transaction_cost(&self, transaction: &SanitizedTransaction) -> (u64, u64) {
|
||||
let mut builtin_costs = 0u64;
|
||||
let mut bpf_costs = 0u64;
|
||||
|
||||
for (program_id, instruction) in transaction.message().program_instructions_iter() {
|
||||
let instruction_cost = self.find_instruction_cost(program_id);
|
||||
trace!(
|
||||
"instruction {:?} has cost of {}",
|
||||
instruction,
|
||||
instruction_cost
|
||||
);
|
||||
cost = cost.saturating_add(instruction_cost);
|
||||
// to keep the same behavior, look for builtin first
|
||||
if let Some(builtin_cost) = BUILT_IN_INSTRUCTION_COSTS.get(program_id) {
|
||||
builtin_costs = builtin_costs.saturating_add(*builtin_cost);
|
||||
} else {
|
||||
let instruction_cost = self.find_instruction_cost(program_id);
|
||||
trace!(
|
||||
"instruction {:?} has cost of {}",
|
||||
instruction,
|
||||
instruction_cost
|
||||
);
|
||||
bpf_costs = bpf_costs.saturating_add(instruction_cost);
|
||||
}
|
||||
}
|
||||
cost
|
||||
(builtin_costs, bpf_costs)
|
||||
}
|
||||
|
||||
fn calculate_account_data_size_on_deserialized_system_instruction(
|
||||
|
@ -341,12 +354,13 @@ mod tests {
|
|||
);
|
||||
|
||||
// expected cost for one system transfer instructions
|
||||
let expected_cost = 8;
|
||||
let expected_execution_cost = BUILT_IN_INSTRUCTION_COSTS
|
||||
.get(&system_program::id())
|
||||
.unwrap();
|
||||
|
||||
let mut testee = CostModel::default();
|
||||
testee.upsert_instruction_cost(&system_program::id(), expected_cost);
|
||||
let testee = CostModel::default();
|
||||
assert_eq!(
|
||||
expected_cost,
|
||||
(*expected_execution_cost, 0),
|
||||
testee.get_transaction_cost(&simple_transaction)
|
||||
);
|
||||
}
|
||||
|
@ -368,12 +382,13 @@ mod tests {
|
|||
debug!("many transfer transaction {:?}", tx);
|
||||
|
||||
// expected cost for two system transfer instructions
|
||||
let program_cost = 8;
|
||||
let program_cost = BUILT_IN_INSTRUCTION_COSTS
|
||||
.get(&system_program::id())
|
||||
.unwrap();
|
||||
let expected_cost = program_cost * 2;
|
||||
|
||||
let mut testee = CostModel::default();
|
||||
testee.upsert_instruction_cost(&system_program::id(), program_cost);
|
||||
assert_eq!(expected_cost, testee.get_transaction_cost(&tx));
|
||||
let testee = CostModel::default();
|
||||
assert_eq!((expected_cost, 0), testee.get_transaction_cost(&tx));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -405,7 +420,7 @@ mod tests {
|
|||
|
||||
// expected cost for two random/unknown program is
|
||||
let expected_cost = testee.instruction_execution_cost_table.get_default_units() * 2;
|
||||
assert_eq!(expected_cost, result);
|
||||
assert_eq!((0, expected_cost), result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -472,13 +487,14 @@ mod tests {
|
|||
));
|
||||
|
||||
let expected_account_cost = WRITE_LOCK_UNITS * 2;
|
||||
let expected_execution_cost = 8;
|
||||
let expected_execution_cost = BUILT_IN_INSTRUCTION_COSTS
|
||||
.get(&system_program::id())
|
||||
.unwrap();
|
||||
|
||||
let mut cost_model = CostModel::default();
|
||||
cost_model.upsert_instruction_cost(&system_program::id(), expected_execution_cost);
|
||||
let cost_model = CostModel::default();
|
||||
let tx_cost = cost_model.calculate_cost(&tx);
|
||||
assert_eq!(expected_account_cost, tx_cost.write_lock_cost);
|
||||
assert_eq!(expected_execution_cost, tx_cost.execution_cost);
|
||||
assert_eq!(*expected_execution_cost, tx_cost.builtins_execution_cost);
|
||||
assert_eq!(2, tx_cost.writable_accounts.len());
|
||||
}
|
||||
|
||||
|
@ -575,14 +591,14 @@ mod tests {
|
|||
assert_eq!(*cost, cost_model.find_instruction_cost(id));
|
||||
}
|
||||
|
||||
// verify built-in programs
|
||||
// verify built-in programs are not in bpf_costs
|
||||
assert!(cost_model
|
||||
.instruction_execution_cost_table
|
||||
.get_cost(&system_program::id())
|
||||
.is_some());
|
||||
.is_none());
|
||||
assert!(cost_model
|
||||
.instruction_execution_cost_table
|
||||
.get_cost(&solana_vote_program::id())
|
||||
.is_some());
|
||||
.is_none());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -296,7 +296,7 @@ mod tests {
|
|||
system_transaction::transfer(mint_keypair, &keypair.pubkey(), 2, *start_hash),
|
||||
);
|
||||
let mut tx_cost = TransactionCost::new_with_capacity(1);
|
||||
tx_cost.execution_cost = 5;
|
||||
tx_cost.bpf_execution_cost = 5;
|
||||
tx_cost.writable_accounts.push(mint_keypair.pubkey());
|
||||
|
||||
(simple_transaction, tx_cost)
|
||||
|
@ -324,7 +324,7 @@ mod tests {
|
|||
)
|
||||
.unwrap();
|
||||
let mut tx_cost = TransactionCost::new_with_capacity(1);
|
||||
tx_cost.execution_cost = 10;
|
||||
tx_cost.bpf_execution_cost = 10;
|
||||
tx_cost.writable_accounts.push(mint_keypair.pubkey());
|
||||
tx_cost.is_simple_vote = true;
|
||||
|
||||
|
@ -627,7 +627,7 @@ mod tests {
|
|||
{
|
||||
let tx_cost = TransactionCost {
|
||||
writable_accounts: vec![acct1, acct2, acct3],
|
||||
execution_cost: cost,
|
||||
bpf_execution_cost: cost,
|
||||
..TransactionCost::default()
|
||||
};
|
||||
assert!(testee.try_add(&tx_cost).is_ok());
|
||||
|
@ -645,7 +645,7 @@ mod tests {
|
|||
{
|
||||
let tx_cost = TransactionCost {
|
||||
writable_accounts: vec![acct2],
|
||||
execution_cost: cost,
|
||||
bpf_execution_cost: cost,
|
||||
..TransactionCost::default()
|
||||
};
|
||||
assert!(testee.try_add(&tx_cost).is_ok());
|
||||
|
@ -665,7 +665,7 @@ mod tests {
|
|||
{
|
||||
let tx_cost = TransactionCost {
|
||||
writable_accounts: vec![acct1, acct2],
|
||||
execution_cost: cost,
|
||||
bpf_execution_cost: cost,
|
||||
..TransactionCost::default()
|
||||
};
|
||||
assert!(testee.try_add(&tx_cost).is_err());
|
||||
|
|
|
@ -16,7 +16,8 @@ pub enum TransactionCostMetrics {
|
|||
signature_cost: u64,
|
||||
write_lock_cost: u64,
|
||||
data_bytes_cost: u64,
|
||||
execution_cost: u64,
|
||||
builtins_execution_cost: u64,
|
||||
bpf_execution_cost: u64,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -51,7 +52,8 @@ impl TransactionCostMetricsSender {
|
|||
signature_cost: cost.signature_cost,
|
||||
write_lock_cost: cost.write_lock_cost,
|
||||
data_bytes_cost: cost.data_bytes_cost,
|
||||
execution_cost: cost.execution_cost,
|
||||
builtins_execution_cost: cost.builtins_execution_cost,
|
||||
bpf_execution_cost: cost.bpf_execution_cost,
|
||||
})
|
||||
.unwrap_or_else(|err| {
|
||||
warn!(
|
||||
|
@ -92,7 +94,8 @@ impl TransactionCostMetricsService {
|
|||
signature_cost,
|
||||
write_lock_cost,
|
||||
data_bytes_cost,
|
||||
execution_cost,
|
||||
builtins_execution_cost,
|
||||
bpf_execution_cost,
|
||||
} => {
|
||||
// report transaction cost details per slot|signature
|
||||
datapoint_trace!(
|
||||
|
@ -102,7 +105,12 @@ impl TransactionCostMetricsService {
|
|||
("signature_cost", signature_cost as i64, i64),
|
||||
("write_lock_cost", write_lock_cost as i64, i64),
|
||||
("data_bytes_cost", data_bytes_cost as i64, i64),
|
||||
("execution_cost", execution_cost as i64, i64),
|
||||
(
|
||||
"builtins_execution_cost",
|
||||
builtins_execution_cost as i64,
|
||||
i64
|
||||
),
|
||||
("bpf_execution_cost", bpf_execution_cost as i64, i64),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue