diff --git a/executor/src/transactions.rs b/executor/src/transactions.rs index ea8cd39..3897709 100644 --- a/executor/src/transactions.rs +++ b/executor/src/transactions.rs @@ -809,7 +809,8 @@ impl TransactionBuilder { alt: Option, ) -> anyhow::Result> { let chain_id = get_chain_id(&tx_data.envelope.tx); - let tx_hash = tx_data.envelope.tx_hash(); + let tx_hash = *tx_data.envelope.tx_hash(); + let mut tx_data = tx_data; let mut iter_info = match iter_info { Some(iter_info) if iter_info.is_finished() => { tracing::debug!(%tx_hash, "iterations finished"); @@ -817,25 +818,7 @@ impl TransactionBuilder { } Some(info) => info, None => { - let build_tx = |iter_info: &mut IterInfo| { - let mut txs = Vec::new(); - while !iter_info.is_finished() { - let ix = self.build_step(BuildStep::collect( - from_data, - iter_info, - &tx_data, - *holder.pubkey(), - ))?; - txs.push(Transaction::new_with_payer( - &with_budget(ix, MAX_COMPUTE_UNITS), - Some(&self.pubkey()), - )); - } - Ok(txs) - }; - - self.emulator - .calculate_iterations(tx_hash, &tx_data.emulate, build_tx) + self.prepare_iterations(&mut tx_data, *holder.pubkey(), from_data) .await? } }; @@ -860,6 +843,56 @@ impl TransactionBuilder { .map(Some) } + async fn prepare_iterations( + &self, + tx_data: &mut TxData, + holder: Pubkey, + from_data: bool, + ) -> anyhow::Result { + // This is a huge number just to make sure we do not get stuck + // in an infinite missing accouunt error. + const SIMULATE_RETRIES: usize = 20; + + let tx_hash = *tx_data.envelope.tx_hash(); + for attempt in 0..SIMULATE_RETRIES { + let build_tx = |iter_info: &mut IterInfo| { + let mut txs = Vec::new(); + while !iter_info.is_finished() { + let ix = + self.build_step(BuildStep::collect(from_data, iter_info, tx_data, holder))?; + txs.push(Transaction::new_with_payer( + &with_budget(ix, MAX_COMPUTE_UNITS), + Some(&self.pubkey()), + )); + } + Ok(txs) + }; + + match self + .emulator + .calculate_iterations(&tx_hash, &tx_data.emulate, build_tx) + .await + { + Ok(info) => return Ok(info), + Err(emulator::Error::MissingAccount(pubkey)) => { + tracing::debug!(%tx_hash, ?pubkey, attempt, "adding missing account"); + tx_data.emulate.solana_accounts.push(NeonSolanaAccount { + pubkey, + is_writable: true, + is_legacy: false, + }) + } + Err(emulator::Error::Other(err)) => return Err(err), + } + } + let iter_info = self.emulator.default_iter_info(&tx_data.emulate); + tracing::warn!( + %tx_hash, ?iter_info, + "selected default iter info after {SIMULATE_RETRIES} unsuccessful attempts" + ); + Ok(iter_info) + } + async fn recovered_step( &self, tx_hash: B256, diff --git a/executor/src/transactions/emulator.rs b/executor/src/transactions/emulator.rs index f190e61..4a09340 100644 --- a/executor/src/transactions/emulator.rs +++ b/executor/src/transactions/emulator.rs @@ -11,12 +11,23 @@ use rust_decimal_macros::dec; use solana_sdk::instruction::{Instruction, InstructionError}; use solana_sdk::pubkey::Pubkey; use solana_sdk::transaction::{Transaction, TransactionError}; +use thiserror::Error; use common::convert::ToNeon; use neon_api::{NeonApi, SimulateConfig}; +use crate::transactions::preflight_error::try_extract_missing_account; + use super::{MAX_COMPUTE_UNITS, MAX_HEAP_SIZE}; +#[derive(Debug, Error)] +pub(super) enum Error { + #[error("missing account: {0}")] + MissingAccount(Pubkey), + #[error("{0}")] + Other(#[from] anyhow::Error), +} + #[derive(Clone, Debug)] pub(super) struct IterInfo { step_count: u32, @@ -158,7 +169,7 @@ impl Emulator { tx_hash: &B256, emulate: &EmulateResponse, f: impl FnMut(&mut IterInfo) -> anyhow::Result>, - ) -> anyhow::Result { + ) -> Result { const RETRIES: usize = 10; let mut f = f; @@ -177,8 +188,6 @@ impl Emulator { break; } - // let exec_iter = - // (total_steps / iter_steps) + if total_steps % iter_steps > 1 { 1 } else { 0 }; let iterations = exec_iter + wrap_iter; tracing::debug!(%tx_hash, iter_steps, total_steps, iterations, "testing iter_info"); @@ -194,6 +203,14 @@ impl Emulator { tracing::debug!(%tx_hash, try_idx = retry, "simulation errored"); for (tx_idx, res) in res.iter().enumerate() { tracing::debug!(%tx_hash, tx_idx, try_idx = retry, ?res, "simulation report"); + if let Some(key) = res + .logs + .iter() + .rev() + .find_map(|log| try_extract_missing_account(log)) + { + return Err(Error::MissingAccount(key)); + } } } @@ -213,15 +230,6 @@ impl Emulator { return Ok(iter_info); } - // let ratio = dec!(0.9).min( - // Decimal::from(max_cu_limit) - // .checked_div(used_cu_limit.into()) - // .unwrap_or(Decimal::MAX), - // ); - // iter_steps = self - // .evm_steps_min - // .load(Relaxed) - // .max(ratio.saturating_mul(iter_steps.into()).try_into()?); exec_iter += 1; iter_steps = (total_steps / exec_iter) + if total_steps % exec_iter > 0 { 1 } else { 0 }; diff --git a/executor/src/transactions/preflight_error.rs b/executor/src/transactions/preflight_error.rs index d7794f3..ae221b1 100644 --- a/executor/src/transactions/preflight_error.rs +++ b/executor/src/transactions/preflight_error.rs @@ -81,7 +81,7 @@ fn extract_transaction_err(err: &ClientErrorKind) -> Option<&TransactionError> { } } -fn try_extract_missing_account(log: &str) -> Option { +pub fn try_extract_missing_account(log: &str) -> Option { let log = log.strip_prefix("Program log: panicked at 'address ")?; let end = log.find(" must be present in the transaction'")?; log[0..end].parse().ok()