Transaction simulation includes cost (#18715)
This commit is contained in:
parent
ae5ad5cf9b
commit
74539020b4
|
@ -328,6 +328,7 @@ pub struct RpcSimulateTransactionResult {
|
||||||
pub err: Option<TransactionError>,
|
pub err: Option<TransactionError>,
|
||||||
pub logs: Option<Vec<String>>,
|
pub logs: Option<Vec<String>>,
|
||||||
pub accounts: Option<Vec<Option<UiAccount>>>,
|
pub accounts: Option<Vec<Option<UiAccount>>>,
|
||||||
|
pub units_consumed: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
|
|
@ -43,7 +43,7 @@ use {
|
||||||
solana_runtime::{
|
solana_runtime::{
|
||||||
accounts::AccountAddressFilter,
|
accounts::AccountAddressFilter,
|
||||||
accounts_index::{AccountIndex, AccountSecondaryIndexes, IndexKey},
|
accounts_index::{AccountIndex, AccountSecondaryIndexes, IndexKey},
|
||||||
bank::Bank,
|
bank::{Bank, TransactionSimulationResult},
|
||||||
bank_forks::BankForks,
|
bank_forks::BankForks,
|
||||||
commitment::{BlockCommitmentArray, BlockCommitmentCache, CommitmentSlots},
|
commitment::{BlockCommitmentArray, BlockCommitmentCache, CommitmentSlots},
|
||||||
inline_spl_token_v2_0::{SPL_TOKEN_ACCOUNT_MINT_OFFSET, SPL_TOKEN_ACCOUNT_OWNER_OFFSET},
|
inline_spl_token_v2_0::{SPL_TOKEN_ACCOUNT_MINT_OFFSET, SPL_TOKEN_ACCOUNT_OWNER_OFFSET},
|
||||||
|
@ -3029,7 +3029,13 @@ pub mod rpc_full {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let (Err(err), logs, _) = preflight_bank.simulate_transaction(&transaction) {
|
if let TransactionSimulationResult {
|
||||||
|
result: Err(err),
|
||||||
|
logs,
|
||||||
|
post_simulation_accounts: _,
|
||||||
|
units_consumed,
|
||||||
|
} = preflight_bank.simulate_transaction(&transaction)
|
||||||
|
{
|
||||||
match err {
|
match err {
|
||||||
TransactionError::BlockhashNotFound => {
|
TransactionError::BlockhashNotFound => {
|
||||||
inc_new_counter_info!("rpc-send-tx_err-blockhash-not-found", 1);
|
inc_new_counter_info!("rpc-send-tx_err-blockhash-not-found", 1);
|
||||||
|
@ -3044,6 +3050,7 @@ pub mod rpc_full {
|
||||||
err: Some(err),
|
err: Some(err),
|
||||||
logs: Some(logs),
|
logs: Some(logs),
|
||||||
accounts: None,
|
accounts: None,
|
||||||
|
units_consumed: Some(units_consumed),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
.into());
|
.into());
|
||||||
|
@ -3087,7 +3094,12 @@ pub mod rpc_full {
|
||||||
if config.replace_recent_blockhash {
|
if config.replace_recent_blockhash {
|
||||||
transaction.message.recent_blockhash = bank.last_blockhash();
|
transaction.message.recent_blockhash = bank.last_blockhash();
|
||||||
}
|
}
|
||||||
let (result, logs, post_simulation_accounts) = bank.simulate_transaction(&transaction);
|
let TransactionSimulationResult {
|
||||||
|
result,
|
||||||
|
logs,
|
||||||
|
post_simulation_accounts,
|
||||||
|
units_consumed,
|
||||||
|
} = bank.simulate_transaction(&transaction);
|
||||||
|
|
||||||
let accounts = if let Some(config_accounts) = config.accounts {
|
let accounts = if let Some(config_accounts) = config.accounts {
|
||||||
let accounts_encoding = config_accounts
|
let accounts_encoding = config_accounts
|
||||||
|
@ -3142,6 +3154,7 @@ pub mod rpc_full {
|
||||||
err: result.err(),
|
err: result.err(),
|
||||||
logs: Some(logs),
|
logs: Some(logs),
|
||||||
accounts,
|
accounts,
|
||||||
|
units_consumed: Some(units_consumed),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -5183,7 +5196,8 @@ pub mod tests {
|
||||||
"logs":[
|
"logs":[
|
||||||
"Program 11111111111111111111111111111111 invoke [1]",
|
"Program 11111111111111111111111111111111 invoke [1]",
|
||||||
"Program 11111111111111111111111111111111 success"
|
"Program 11111111111111111111111111111111 success"
|
||||||
]
|
],
|
||||||
|
"unitsConsumed":0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"id": 1,
|
"id": 1,
|
||||||
|
@ -5262,10 +5276,15 @@ pub mod tests {
|
||||||
"jsonrpc": "2.0",
|
"jsonrpc": "2.0",
|
||||||
"result": {
|
"result": {
|
||||||
"context":{"slot":0},
|
"context":{"slot":0},
|
||||||
"value":{"accounts": null, "err":null, "logs":[
|
"value":{
|
||||||
"Program 11111111111111111111111111111111 invoke [1]",
|
"accounts":null,
|
||||||
"Program 11111111111111111111111111111111 success"
|
"err":null,
|
||||||
]}
|
"logs":[
|
||||||
|
"Program 11111111111111111111111111111111 invoke [1]",
|
||||||
|
"Program 11111111111111111111111111111111 success"
|
||||||
|
],
|
||||||
|
"unitsConsumed":0
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"id": 1,
|
"id": 1,
|
||||||
});
|
});
|
||||||
|
@ -5285,10 +5304,15 @@ pub mod tests {
|
||||||
"jsonrpc": "2.0",
|
"jsonrpc": "2.0",
|
||||||
"result": {
|
"result": {
|
||||||
"context":{"slot":0},
|
"context":{"slot":0},
|
||||||
"value":{"accounts": null, "err":null, "logs":[
|
"value":{
|
||||||
"Program 11111111111111111111111111111111 invoke [1]",
|
"accounts":null,
|
||||||
"Program 11111111111111111111111111111111 success"
|
"err":null,
|
||||||
]}
|
"logs":[
|
||||||
|
"Program 11111111111111111111111111111111 invoke [1]",
|
||||||
|
"Program 11111111111111111111111111111111 success"
|
||||||
|
],
|
||||||
|
"unitsConsumed":0
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"id": 1,
|
"id": 1,
|
||||||
});
|
});
|
||||||
|
@ -5333,7 +5357,12 @@ pub mod tests {
|
||||||
"jsonrpc":"2.0",
|
"jsonrpc":"2.0",
|
||||||
"result": {
|
"result": {
|
||||||
"context":{"slot":0},
|
"context":{"slot":0},
|
||||||
"value":{"err": "BlockhashNotFound", "accounts": null, "logs":[]}
|
"value":{
|
||||||
|
"err":"BlockhashNotFound",
|
||||||
|
"accounts":null,
|
||||||
|
"logs":[],
|
||||||
|
"unitsConsumed":0
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"id":1
|
"id":1
|
||||||
});
|
});
|
||||||
|
@ -5354,10 +5383,15 @@ pub mod tests {
|
||||||
"jsonrpc": "2.0",
|
"jsonrpc": "2.0",
|
||||||
"result": {
|
"result": {
|
||||||
"context":{"slot":0},
|
"context":{"slot":0},
|
||||||
"value":{"accounts": null, "err":null, "logs":[
|
"value":{
|
||||||
"Program 11111111111111111111111111111111 invoke [1]",
|
"accounts":null,
|
||||||
"Program 11111111111111111111111111111111 success"
|
"err":null,
|
||||||
]}
|
"logs":[
|
||||||
|
"Program 11111111111111111111111111111111 invoke [1]",
|
||||||
|
"Program 11111111111111111111111111111111 success"
|
||||||
|
],
|
||||||
|
"unitsConsumed":0
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"id": 1,
|
"id": 1,
|
||||||
});
|
});
|
||||||
|
@ -5698,7 +5732,7 @@ pub mod tests {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
res,
|
res,
|
||||||
Some(
|
Some(
|
||||||
r#"{"jsonrpc":"2.0","error":{"code":-32002,"message":"Transaction simulation failed: Blockhash not found","data":{"accounts":null,"err":"BlockhashNotFound","logs":[]}},"id":1}"#.to_string(),
|
r#"{"jsonrpc":"2.0","error":{"code":-32002,"message":"Transaction simulation failed: Blockhash not found","data":{"accounts":null,"err":"BlockhashNotFound","logs":[],"unitsConsumed":0}},"id":1}"#.to_string(),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -138,7 +138,6 @@ pub const MAX_LEADER_SCHEDULE_STAKES: Epoch = 5;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq)]
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
pub struct RentDebits(pub Vec<(Pubkey, RewardInfo)>);
|
pub struct RentDebits(pub Vec<(Pubkey, RewardInfo)>);
|
||||||
|
|
||||||
impl RentDebits {
|
impl RentDebits {
|
||||||
pub fn push(&mut self, account: &Pubkey, rent: u64, post_balance: u64) {
|
pub fn push(&mut self, account: &Pubkey, rent: u64, post_balance: u64) {
|
||||||
if rent != 0 {
|
if rent != 0 {
|
||||||
|
@ -168,7 +167,6 @@ pub struct ExecuteTimings {
|
||||||
pub num_execute_batches: u64,
|
pub num_execute_batches: u64,
|
||||||
pub details: ExecuteDetailsTimings,
|
pub details: ExecuteDetailsTimings,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExecuteTimings {
|
impl ExecuteTimings {
|
||||||
pub fn accumulate(&mut self, other: &ExecuteTimings) {
|
pub fn accumulate(&mut self, other: &ExecuteTimings) {
|
||||||
self.check_us += other.check_us;
|
self.check_us += other.check_us;
|
||||||
|
@ -415,15 +413,15 @@ impl CachedExecutors {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TxComputeMeter {
|
pub struct TransactionComputeMeter {
|
||||||
remaining: u64,
|
remaining: u64,
|
||||||
}
|
}
|
||||||
impl TxComputeMeter {
|
impl TransactionComputeMeter {
|
||||||
pub fn new(cap: u64) -> Self {
|
pub fn new(cap: u64) -> Self {
|
||||||
Self { remaining: cap }
|
Self { remaining: cap }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl ComputeMeter for TxComputeMeter {
|
impl ComputeMeter for TransactionComputeMeter {
|
||||||
fn consume(&mut self, amount: u64) -> std::result::Result<(), InstructionError> {
|
fn consume(&mut self, amount: u64) -> std::result::Result<(), InstructionError> {
|
||||||
let exceeded = self.remaining < amount;
|
let exceeded = self.remaining < amount;
|
||||||
self.remaining = self.remaining.saturating_sub(amount);
|
self.remaining = self.remaining.saturating_sub(amount);
|
||||||
|
@ -524,6 +522,12 @@ pub struct TransactionResults {
|
||||||
pub overwritten_vote_accounts: Vec<OverwrittenVoteAccount>,
|
pub overwritten_vote_accounts: Vec<OverwrittenVoteAccount>,
|
||||||
pub rent_debits: Vec<RentDebits>,
|
pub rent_debits: Vec<RentDebits>,
|
||||||
}
|
}
|
||||||
|
pub struct TransactionSimulationResult {
|
||||||
|
pub result: Result<()>,
|
||||||
|
pub logs: TransactionLogMessages,
|
||||||
|
pub post_simulation_accounts: Vec<(Pubkey, AccountSharedData)>,
|
||||||
|
pub units_consumed: u64,
|
||||||
|
}
|
||||||
pub struct TransactionBalancesSet {
|
pub struct TransactionBalancesSet {
|
||||||
pub pre_balances: TransactionBalances,
|
pub pre_balances: TransactionBalances,
|
||||||
pub post_balances: TransactionBalances,
|
pub post_balances: TransactionBalances,
|
||||||
|
@ -2705,36 +2709,36 @@ impl Bank {
|
||||||
|
|
||||||
pub(crate) fn prepare_simulation_batch<'a, 'b>(
|
pub(crate) fn prepare_simulation_batch<'a, 'b>(
|
||||||
&'a self,
|
&'a self,
|
||||||
tx: SanitizedTransaction<'b>,
|
transaction: SanitizedTransaction<'b>,
|
||||||
) -> TransactionBatch<'a, 'b> {
|
) -> TransactionBatch<'a, 'b> {
|
||||||
let mut batch = TransactionBatch::new(vec![Ok(())], self, Cow::Owned(vec![tx]));
|
let mut batch = TransactionBatch::new(vec![Ok(())], self, Cow::Owned(vec![transaction]));
|
||||||
batch.needs_unlock = false;
|
batch.needs_unlock = false;
|
||||||
batch
|
batch
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run transactions against a frozen bank without committing the results
|
/// Run transactions against a frozen bank without committing the results
|
||||||
pub fn simulate_transaction(
|
pub fn simulate_transaction(&self, transaction: &Transaction) -> TransactionSimulationResult {
|
||||||
&self,
|
|
||||||
transaction: &Transaction,
|
|
||||||
) -> (
|
|
||||||
Result<()>,
|
|
||||||
TransactionLogMessages,
|
|
||||||
Vec<(Pubkey, AccountSharedData)>,
|
|
||||||
) {
|
|
||||||
assert!(self.is_frozen(), "simulation bank must be frozen");
|
assert!(self.is_frozen(), "simulation bank must be frozen");
|
||||||
|
|
||||||
let batch = match SanitizedTransaction::try_from(transaction) {
|
let batch = match SanitizedTransaction::try_from(transaction) {
|
||||||
Ok(sanitized_tx) => self.prepare_simulation_batch(sanitized_tx),
|
Ok(sanitized_tx) => self.prepare_simulation_batch(sanitized_tx),
|
||||||
Err(err) => return (Err(err), vec![], vec![]),
|
Err(err) => {
|
||||||
|
return TransactionSimulationResult {
|
||||||
|
result: Err(err),
|
||||||
|
logs: vec![],
|
||||||
|
post_simulation_accounts: vec![],
|
||||||
|
units_consumed: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut timings = ExecuteTimings::default();
|
let mut timings = ExecuteTimings::default();
|
||||||
|
|
||||||
let (
|
let (
|
||||||
loaded_txs,
|
loaded_transactions,
|
||||||
executed,
|
executed,
|
||||||
_inner_instructions,
|
_inner_instructions,
|
||||||
log_messages,
|
logs,
|
||||||
_retryable_transactions,
|
_retryable_transactions,
|
||||||
_transaction_count,
|
_transaction_count,
|
||||||
_signature_count,
|
_signature_count,
|
||||||
|
@ -2749,9 +2753,9 @@ impl Bank {
|
||||||
&mut timings,
|
&mut timings,
|
||||||
);
|
);
|
||||||
|
|
||||||
let transaction_result = executed[0].0.clone().map(|_| ());
|
let result = executed[0].0.clone().map(|_| ());
|
||||||
let log_messages = log_messages.get(0).cloned().flatten().unwrap_or_default();
|
let logs = logs.get(0).cloned().flatten().unwrap_or_default();
|
||||||
let post_transaction_accounts = loaded_txs
|
let post_simulation_accounts = loaded_transactions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.next()
|
.next()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -2760,9 +2764,22 @@ impl Bank {
|
||||||
.map(|loaded_transaction| loaded_transaction.accounts.into_iter().collect::<Vec<_>>())
|
.map(|loaded_transaction| loaded_transaction.accounts.into_iter().collect::<Vec<_>>())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let units_consumed = timings
|
||||||
|
.details
|
||||||
|
.per_program_timings
|
||||||
|
.iter()
|
||||||
|
.fold(0, |acc, (_, program_timing)| {
|
||||||
|
acc + program_timing.accumulated_units
|
||||||
|
});
|
||||||
|
|
||||||
debug!("simulate_transaction: {:?}", timings);
|
debug!("simulate_transaction: {:?}", timings);
|
||||||
|
|
||||||
(transaction_result, log_messages, post_transaction_accounts)
|
TransactionSimulationResult {
|
||||||
|
result,
|
||||||
|
logs,
|
||||||
|
post_simulation_accounts,
|
||||||
|
units_consumed,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unlock_accounts(&self, batch: &mut TransactionBatch) {
|
pub fn unlock_accounts(&self, batch: &mut TransactionBatch) {
|
||||||
|
@ -3245,7 +3262,7 @@ impl Bank {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let compute_meter = Rc::new(RefCell::new(TxComputeMeter::new(
|
let compute_meter = Rc::new(RefCell::new(TransactionComputeMeter::new(
|
||||||
bpf_compute_budget.max_units,
|
bpf_compute_budget.max_units,
|
||||||
)));
|
)));
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue