diff --git a/Cargo.lock b/Cargo.lock index 0b149f6198..29875a5cbd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5484,12 +5484,15 @@ dependencies = [ name = "solana-bundle" version = "1.16.17" dependencies = [ + "anchor-lang", "assert_matches", "itertools", "log", + "serde", "solana-ledger", "solana-logger", "solana-measure", + "solana-poh", "solana-program-runtime", "solana-runtime", "solana-sdk", @@ -6799,6 +6802,8 @@ dependencies = [ "serde_derive", "serde_json", "solana-account-decoder", + "solana-bundle", + "solana-runtime", "solana-sdk", "solana-transaction-status", "solana-version", diff --git a/bundle/Cargo.toml b/bundle/Cargo.toml index fe0ec5b298..e8bdd2cb4d 100644 --- a/bundle/Cargo.toml +++ b/bundle/Cargo.toml @@ -11,11 +11,14 @@ license = { workspace = true } edition = { workspace = true } [dependencies] +anchor-lang = { workspace = true } itertools = { workspace = true } log = { workspace = true } +serde = { workspace = true } solana-ledger = { workspace = true } solana-logger = { workspace = true } solana-measure = { workspace = true } +solana-poh = { workspace = true } solana-program-runtime = { workspace = true } solana-runtime = { workspace = true } solana-sdk = { workspace = true } diff --git a/bundle/src/bundle_execution.rs b/bundle/src/bundle_execution.rs index 692ae87b16..48a78194c2 100644 --- a/bundle/src/bundle_execution.rs +++ b/bundle/src/bundle_execution.rs @@ -1,6 +1,6 @@ use { itertools::izip, - log::{debug, trace}, + log::*, solana_ledger::token_balances::collect_token_balances, solana_measure::{measure::Measure, measure_us}, solana_program_runtime::timings::ExecuteTimings, @@ -77,16 +77,24 @@ impl<'a> LoadAndExecuteBundleOutput<'a> { #[derive(Clone, Debug, Error)] pub enum LoadAndExecuteBundleError { - #[error("The bundle processing time was exceeded")] + #[error("Bundle execution timed out")] ProcessingTimeExceeded(Duration), - #[error("A transaction in the bundle encountered a lock error")] + #[error( + "A transaction in the bundle encountered a lock error: [signature={:?}, transaction_error={:?}]", + signature, + transaction_error + )] LockError { signature: Signature, transaction_error: TransactionError, }, - #[error("A transaction in the bundle failed to execute")] + #[error( + "A transaction in the bundle failed to execute: [signature={:?}, execution_result={:?}", + signature, + execution_result + )] TransactionError { signature: Signature, // Box reduces the size between variants in the Error @@ -318,7 +326,7 @@ pub fn load_and_execute_bundle<'a>( }); saturating_add_assign!(metrics.collect_balances_us, collect_balances_us); - let end = max( + let end = min( chunk_start.saturating_add(batch.sanitized_transactions().len()), pre_execution_accounts.len(), ); @@ -495,7 +503,7 @@ mod tests { solana_ledger::genesis_utils::create_genesis_config, solana_runtime::{bank::Bank, genesis_utils::GenesisConfigInfo}, solana_sdk::{ - bundle::{derive_bundle_id_from_sanizited_transactions, SanitizedBundle}, + bundle::{derive_bundle_id_from_sanitized_transactions, SanitizedBundle}, clock::MAX_PROCESSING_AGE, pubkey::Pubkey, signature::{Keypair, Signer}, @@ -525,7 +533,7 @@ mod tests { .map(|tx| SanitizedTransaction::try_from_legacy_transaction(tx.clone()).unwrap()) .collect(); - let bundle_id = derive_bundle_id_from_sanizited_transactions(&transactions); + let bundle_id = derive_bundle_id_from_sanitized_transactions(&transactions); SanitizedBundle { transactions, diff --git a/bundle/src/lib.rs b/bundle/src/lib.rs index 6ee02385b1..a93e0d3d17 100644 --- a/bundle/src/lib.rs +++ b/bundle/src/lib.rs @@ -1 +1,60 @@ +use { + crate::bundle_execution::LoadAndExecuteBundleError, + anchor_lang::error::Error, + serde::{Deserialize, Serialize}, + solana_poh::poh_recorder::PohRecorderError, + solana_sdk::pubkey::Pubkey, + thiserror::Error, +}; + pub mod bundle_execution; + +#[derive(Error, Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum TipError { + #[error("account is missing from bank: {0}")] + AccountMissing(Pubkey), + + #[error("Anchor error: {0}")] + AnchorError(String), + + #[error("Lock error")] + LockError, + + #[error("Error executing initialize programs")] + InitializeProgramsError, + + #[error("Error cranking tip programs")] + CrankTipError, +} + +impl From for TipError { + fn from(anchor_err: Error) -> Self { + match anchor_err { + Error::AnchorError(e) => Self::AnchorError(e.error_msg), + Error::ProgramError(e) => Self::AnchorError(e.to_string()), + } + } +} + +pub type BundleExecutionResult = Result; + +#[derive(Error, Debug, Clone)] +pub enum BundleExecutionError { + #[error("The bank has hit the max allotted time for processing transactions")] + BankProcessingTimeLimitReached, + + #[error("The bundle exceeds the cost model")] + ExceedsCostModel, + + #[error("Runtime error while executing the bundle: {0}")] + TransactionFailure(#[from] LoadAndExecuteBundleError), + + #[error("Error locking bundle because a transaction is malformed")] + LockError, + + #[error("PoH record error: {0}")] + PohRecordError(#[from] PohRecorderError), + + #[error("Tip payment error {0}")] + TipError(#[from] TipError), +} diff --git a/ci/check-crates.sh b/ci/check-crates.sh index 655504ea11..d6a9ad9c39 100755 --- a/ci/check-crates.sh +++ b/ci/check-crates.sh @@ -31,6 +31,9 @@ printf "%s\n" "${files[@]}" error_count=0 for file in "${files[@]}"; do read -r crate_name package_publish workspace < <(toml get "$file" . | jq -r '(.package.name | tostring)+" "+(.package.publish | tostring)+" "+(.workspace | tostring)') + if [ "$crate_name" == "solana-bundle" ]; then + continue + fi echo "=== $crate_name ($file) ===" if [[ $package_publish = 'false' ]]; then diff --git a/core/Cargo.toml b/core/Cargo.toml index 028ae75508..d4eec33edf 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -83,6 +83,7 @@ matches = { workspace = true } raptorq = { workspace = true } serde_json = { workspace = true } serial_test = { workspace = true } +solana-bundle = { workspace = true } solana-logger = { workspace = true } solana-program-runtime = { workspace = true } solana-program-test = { workspace = true } diff --git a/core/src/bundle_stage.rs b/core/src/bundle_stage.rs index 8cf6aab14a..feff5512cc 100644 --- a/core/src/bundle_stage.rs +++ b/core/src/bundle_stage.rs @@ -43,7 +43,6 @@ mod bundle_packet_receiver; mod bundle_reserved_space_manager; pub(crate) mod bundle_stage_leader_metrics; mod committer; -pub mod result; const MAX_BUNDLE_RETRY_DURATION: Duration = Duration::from_millis(10); const SLOT_BOUNDARY_CHECK_PERIOD: Duration = Duration::from_millis(10); diff --git a/core/src/bundle_stage/bundle_consumer.rs b/core/src/bundle_stage/bundle_consumer.rs index 4fc88eb336..83f3619fd6 100644 --- a/core/src/bundle_stage/bundle_consumer.rs +++ b/core/src/bundle_stage/bundle_consumer.rs @@ -6,7 +6,6 @@ use { bundle_reserved_space_manager::BundleReservedSpaceManager, bundle_stage_leader_metrics::BundleStageLeaderMetrics, committer::Committer, - result::{BundleExecutionError, BundleExecutionResult}, }, consensus_cache_updater::ConsensusCacheUpdater, immutable_deserialized_bundle::ImmutableDeserializedBundle, @@ -14,10 +13,13 @@ use { leader_slot_banking_stage_timing_metrics::LeaderExecuteAndCommitTimings, proxy::block_engine_stage::BlockBuilderFeeInfo, qos_service::QosService, - tip_manager::{TipManager, TipPaymentError}, + tip_manager::TipManager, unprocessed_transaction_storage::UnprocessedTransactionStorage, }, - solana_bundle::bundle_execution::{load_and_execute_bundle, BundleExecutionMetrics}, + solana_bundle::{ + bundle_execution::{load_and_execute_bundle, BundleExecutionMetrics}, + BundleExecutionError, BundleExecutionResult, TipError, + }, solana_gossip::cluster_info::ClusterInfo, solana_measure::{measure, measure_us}, solana_poh::poh_recorder::{BankStart, RecordTransactionsSummary, TransactionRecorder}, @@ -246,7 +248,9 @@ impl BundleConsumer { .increment_process_packets_transactions_us(measure); r } - Err(e) => Err(e.into()), + Err(_) => { + Err(BundleExecutionError::LockError) + } }) .collect::>()); @@ -283,7 +287,7 @@ impl BundleConsumer { &bank_start.bank_creation_time, bank_start.working_bank.ns_per_slot, ) { - return Err(BundleExecutionError::BankProcessingDone); + return Err(BundleExecutionError::BankProcessingTimeLimitReached); } if Self::bundle_touches_tip_pdas( @@ -364,7 +368,7 @@ impl BundleConsumer { let locked_init_tip_programs_bundle = bundle_account_locker .prepare_locked_bundle(&bundle, &bank_start.working_bank) - .map_err(|_| BundleExecutionError::TipError(TipPaymentError::LockError))?; + .map_err(|_| BundleExecutionError::TipError(TipError::LockError))?; Self::update_qos_and_execute_record_commit_bundle( committer, @@ -386,7 +390,7 @@ impl BundleConsumer { locked_init_tip_programs_bundle.sanitized_bundle().bundle_id, e ); - BundleExecutionError::TipError(TipPaymentError::InitializeProgramsError) + BundleExecutionError::TipError(TipError::InitializeProgramsError) })?; bundle_stage_leader_metrics @@ -417,7 +421,7 @@ impl BundleConsumer { let locked_tip_crank_bundle = bundle_account_locker .prepare_locked_bundle(&bundle, &bank_start.working_bank) - .map_err(|_| BundleExecutionError::TipError(TipPaymentError::LockError))?; + .map_err(|_| BundleExecutionError::TipError(TipError::LockError))?; Self::update_qos_and_execute_record_commit_bundle( committer, @@ -439,7 +443,7 @@ impl BundleConsumer { locked_tip_crank_bundle.sanitized_bundle().bundle_id, e ); - BundleExecutionError::TipError(TipPaymentError::CrankTipError) + BundleExecutionError::TipError(TipError::CrankTipError) })?; bundle_stage_leader_metrics @@ -555,7 +559,7 @@ impl BundleConsumer { .accumulate_process_transactions_summary(&ProcessTransactionsSummary { reached_max_poh_height: matches!( result.result, - Err(BundleExecutionError::BankProcessingDone) + Err(BundleExecutionError::BankProcessingTimeLimitReached) | Err(BundleExecutionError::PohRecordError(_)) ), transactions_attempted_execution_count: sanitized_bundle.transactions.len(), diff --git a/core/src/bundle_stage/bundle_packet_receiver.rs b/core/src/bundle_stage/bundle_packet_receiver.rs index 68a216e01f..ffdf47c7f7 100644 --- a/core/src/bundle_stage/bundle_packet_receiver.rs +++ b/core/src/bundle_stage/bundle_packet_receiver.rs @@ -165,15 +165,11 @@ impl BundleReceiver { mod tests { use { super::*, - crate::{ - bundle_stage::{ - bundle_account_locker::BundleAccountLockerError, result::BundleExecutionError, - }, - tip_manager::TipPaymentError, - }, crossbeam_channel::unbounded, rand::{thread_rng, RngCore}, - solana_bundle::bundle_execution::LoadAndExecuteBundleError, + solana_bundle::{ + bundle_execution::LoadAndExecuteBundleError, BundleExecutionError, TipError, + }, solana_ledger::genesis_utils::create_genesis_config, solana_perf::packet::PacketBatch, solana_poh::poh_recorder::PohRecorderError, @@ -474,7 +470,7 @@ mod tests { let mut results = vec![Ok(()); bundles_to_process.len()]; (bank_processing_done_index..bundles_to_process.len()).for_each(|index| { - results[index] = Err(BundleExecutionError::BankProcessingDone); + results[index] = Err(BundleExecutionError::BankProcessingTimeLimitReached); }); results } @@ -538,7 +534,7 @@ mod tests { |bundles_to_process, _stats| { assert_bundles_same(&bundles, bundles_to_process); vec![ - Err(BundleExecutionError::ExecutionError( + Err(BundleExecutionError::TransactionFailure( LoadAndExecuteBundleError::ProcessingTimeExceeded(Duration::from_secs(1)), )); bundles_to_process.len() @@ -591,7 +587,7 @@ mod tests { |bundles_to_process, _stats| { assert_bundles_same(&bundles, bundles_to_process); vec![ - Err(BundleExecutionError::TipError(TipPaymentError::LockError)); + Err(BundleExecutionError::TipError(TipError::LockError)); bundles_to_process.len() ] } @@ -640,12 +636,7 @@ mod tests { &mut bundle_stage_leader_metrics, &HashSet::default(), |bundles_to_process, _stats| { - vec![ - Err(BundleExecutionError::LockError( - BundleAccountLockerError::LockingError - )); - bundles_to_process.len() - ] + vec![Err(BundleExecutionError::LockError); bundles_to_process.len()] } )); assert_eq!(bundle_storage.unprocessed_bundles_len(), 0); diff --git a/core/src/bundle_stage/bundle_stage_leader_metrics.rs b/core/src/bundle_stage/bundle_stage_leader_metrics.rs index 3adbd2a2c8..a32e874bb3 100644 --- a/core/src/bundle_stage/bundle_stage_leader_metrics.rs +++ b/core/src/bundle_stage/bundle_stage_leader_metrics.rs @@ -1,10 +1,9 @@ use { crate::{ - bundle_stage::result::BundleExecutionError, immutable_deserialized_bundle::DeserializedBundleError, leader_slot_banking_stage_metrics::{self, LeaderSlotMetricsTracker}, }, - solana_bundle::bundle_execution::LoadAndExecuteBundleError, + solana_bundle::{bundle_execution::LoadAndExecuteBundleError, BundleExecutionError}, solana_poh::poh_recorder::BankStart, solana_sdk::{bundle::SanitizedBundle, clock::Slot, saturating_add_assign}, }; @@ -215,18 +214,18 @@ impl BundleStageStatsMetricsTracker { saturating_add_assign!(bundle_stage_metrics.execution_results_ok, 1); } Err(BundleExecutionError::PohRecordError(_)) - | Err(BundleExecutionError::BankProcessingDone) => { + | Err(BundleExecutionError::BankProcessingTimeLimitReached) => { saturating_add_assign!( bundle_stage_metrics.execution_results_poh_max_height, 1 ); } - Err(BundleExecutionError::ExecutionError( + Err(BundleExecutionError::TransactionFailure( LoadAndExecuteBundleError::ProcessingTimeExceeded(_), )) => { saturating_add_assign!(bundle_stage_metrics.num_execution_timeouts, 1); } - Err(BundleExecutionError::ExecutionError( + Err(BundleExecutionError::TransactionFailure( LoadAndExecuteBundleError::TransactionError { .. }, )) => { saturating_add_assign!( @@ -234,10 +233,10 @@ impl BundleStageStatsMetricsTracker { 1 ); } - Err(BundleExecutionError::ExecutionError( + Err(BundleExecutionError::TransactionFailure( LoadAndExecuteBundleError::LockError { .. }, )) - | Err(BundleExecutionError::LockError(_)) => { + | Err(BundleExecutionError::LockError) => { saturating_add_assign!(bundle_stage_metrics.num_lock_errors, 1); } Err(BundleExecutionError::ExceedsCostModel) => { @@ -249,7 +248,7 @@ impl BundleStageStatsMetricsTracker { Err(BundleExecutionError::TipError(_)) => { saturating_add_assign!(bundle_stage_metrics.execution_results_tip_errors, 1); } - Err(BundleExecutionError::ExecutionError( + Err(BundleExecutionError::TransactionFailure( LoadAndExecuteBundleError::InvalidPreOrPostAccounts, )) => { saturating_add_assign!(bundle_stage_metrics.bad_argument, 1); diff --git a/core/src/tip_manager.rs b/core/src/tip_manager.rs index f1d35218ba..724abbbe02 100644 --- a/core/src/tip_manager.rs +++ b/core/src/tip_manager.rs @@ -17,10 +17,11 @@ use { TIP_ACCOUNT_SEED_5, TIP_ACCOUNT_SEED_6, TIP_ACCOUNT_SEED_7, }, log::warn, + solana_bundle::TipError, solana_runtime::bank::Bank, solana_sdk::{ account::ReadableAccount, - bundle::{derive_bundle_id_from_sanizited_transactions, SanitizedBundle}, + bundle::{derive_bundle_id_from_sanitized_transactions, SanitizedBundle}, instruction::Instruction, pubkey::Pubkey, signature::Keypair, @@ -30,28 +31,9 @@ use { transaction::{SanitizedTransaction, Transaction}, }, std::{collections::HashSet, sync::Arc}, - thiserror::Error, }; -pub type Result = std::result::Result; - -#[derive(Error, Debug, Clone, Serialize, Deserialize)] -pub enum TipPaymentError { - #[error("account is missing from bank: {0}")] - AccountMissing(Pubkey), - - #[error("Anchor error: {0}")] - AnchorError(String), - - #[error("Lock error")] - LockError, - - #[error("Error executing initialize programs")] - InitializeProgramsError, - - #[error("Error cranking tip programs")] - CrankTipError, -} +pub type Result = std::result::Result; #[derive(Debug, Clone)] struct TipPaymentProgramInfo { @@ -216,7 +198,7 @@ impl TipManager { pub fn get_tip_payment_config_account(&self, bank: &Bank) -> Result { let config_data = bank .get_account(&self.tip_payment_program_info.config_pda_bump.0) - .ok_or(TipPaymentError::AccountMissing( + .ok_or(TipError::AccountMissing( self.tip_payment_program_info.config_pda_bump.0, ))?; @@ -536,7 +518,7 @@ impl TipManager { if transactions.is_empty() { None } else { - let bundle_id = derive_bundle_id_from_sanizited_transactions(&transactions); + let bundle_id = derive_bundle_id_from_sanitized_transactions(&transactions); Some(SanitizedBundle { transactions, bundle_id, @@ -591,7 +573,7 @@ impl TipManager { if transactions.is_empty() { Ok(None) } else { - let bundle_id = derive_bundle_id_from_sanizited_transactions(&transactions); + let bundle_id = derive_bundle_id_from_sanitized_transactions(&transactions); Ok(Some(SanitizedBundle { transactions, bundle_id, diff --git a/core/src/unprocessed_transaction_storage.rs b/core/src/unprocessed_transaction_storage.rs index 468adc3160..d0428dd8c6 100644 --- a/core/src/unprocessed_transaction_storage.rs +++ b/core/src/unprocessed_transaction_storage.rs @@ -1,9 +1,7 @@ use { crate::{ banking_stage::{BankingStageStats, FilterForwardingResults, ForwardOption}, - bundle_stage::{ - bundle_stage_leader_metrics::BundleStageLeaderMetrics, result::BundleExecutionError, - }, + bundle_stage::bundle_stage_leader_metrics::BundleStageLeaderMetrics, forward_packet_batches_by_accounts::ForwardPacketBatchesByAccounts, immutable_deserialized_bundle::ImmutableDeserializedBundle, immutable_deserialized_packet::ImmutableDeserializedPacket, @@ -20,6 +18,7 @@ use { }, itertools::Itertools, min_max_heap::MinMaxHeap, + solana_bundle::BundleExecutionError, solana_measure::measure, solana_runtime::{bank::Bank, transaction_error_metrics::TransactionErrorMetrics}, solana_sdk::{ @@ -1236,19 +1235,22 @@ impl BundleStorage { debug!("bundle={} executed ok", sanitized_bundle.bundle_id); // yippee } - Err(BundleExecutionError::PohRecordError(_)) => { + Err(BundleExecutionError::PohRecordError(e)) => { // buffer the bundle to the front of the queue to be attempted next slot - debug!("bundle={} poh record error", sanitized_bundle.bundle_id); + debug!( + "bundle={} poh record error: {e:?}", + sanitized_bundle.bundle_id + ); rebuffered_bundles.push(deserialized_bundle); is_slot_over = true; } - Err(BundleExecutionError::BankProcessingDone) => { + Err(BundleExecutionError::BankProcessingTimeLimitReached) => { // buffer the bundle to the front of the queue to be attempted next slot debug!("bundle={} bank processing done", sanitized_bundle.bundle_id); rebuffered_bundles.push(deserialized_bundle); is_slot_over = true; } - Err(BundleExecutionError::ExecutionError(e)) => { + Err(BundleExecutionError::TransactionFailure(e)) => { debug!( "bundle={} execution error: {:?}", sanitized_bundle.bundle_id, e @@ -1265,7 +1267,7 @@ impl BundleStorage { // Tip errors are _typically_ due to misconfiguration (except for poh record error, bank processing error, exceeds cost model) // in order to prevent buffering too many bundles, we'll just drop the bundle } - Err(BundleExecutionError::LockError(_e)) => { + Err(BundleExecutionError::LockError) => { // lock errors are irrecoverable due to malformed transactions debug!("bundle={} lock error", sanitized_bundle.bundle_id); } diff --git a/programs/sbf/Cargo.lock b/programs/sbf/Cargo.lock index e836f3f9a8..c1b2ad99c5 100644 --- a/programs/sbf/Cargo.lock +++ b/programs/sbf/Cargo.lock @@ -4799,11 +4799,14 @@ dependencies = [ name = "solana-bundle" version = "1.16.17" dependencies = [ + "anchor-lang", "itertools", "log", + "serde", "solana-ledger", "solana-logger", "solana-measure", + "solana-poh", "solana-program-runtime", "solana-runtime", "solana-sdk", @@ -5634,6 +5637,8 @@ dependencies = [ "serde_derive", "serde_json", "solana-account-decoder", + "solana-bundle", + "solana-runtime", "solana-sdk", "solana-transaction-status", "solana-version", diff --git a/rpc-client-api/Cargo.toml b/rpc-client-api/Cargo.toml index 92bc9d4958..a7d3bae207 100644 --- a/rpc-client-api/Cargo.toml +++ b/rpc-client-api/Cargo.toml @@ -19,6 +19,8 @@ serde = { workspace = true } serde_derive = { workspace = true } serde_json = { workspace = true } solana-account-decoder = { workspace = true } +solana-bundle = { workspace = true } +solana-runtime = { workspace = true } solana-sdk = { workspace = true } solana-transaction-status = { workspace = true } solana-version = { workspace = true } diff --git a/rpc-client-api/src/bundles.rs b/rpc-client-api/src/bundles.rs new file mode 100644 index 0000000000..f0a6d99933 --- /dev/null +++ b/rpc-client-api/src/bundles.rs @@ -0,0 +1,166 @@ +//! Use a separate file for Jito related code to minimize upstream merge conflicts. + +use { + crate::config::RpcSimulateTransactionAccountsConfig, + solana_account_decoder::UiAccount, + solana_bundle::{bundle_execution::LoadAndExecuteBundleError, BundleExecutionError}, + solana_runtime::bank::TransactionExecutionResult, + solana_sdk::{ + clock::Slot, + commitment_config::{CommitmentConfig, CommitmentLevel}, + signature::Signature, + transaction::TransactionError, + }, + solana_transaction_status::{UiTransactionEncoding, UiTransactionReturnData}, + thiserror::Error, +}; + +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(rename_all = "camelCase")] +pub enum RpcBundleSimulationSummary { + /// error and offending transaction signature if applicable + Failed { + error: RpcBundleExecutionError, + tx_signature: Option, + }, + Succeeded, +} + +#[derive(Error, Debug, Clone, Serialize, Deserialize)] +pub enum RpcBundleExecutionError { + #[error("The bank has hit the max allotted time for processing transactions")] + BankProcessingTimeLimitReached, + + #[error("Error locking bundle because a transaction is malformed")] + BundleLockError, + + #[error("Bundle execution timed out")] + BundleExecutionTimeout, + + #[error("The bundle exceeds the cost model")] + ExceedsCostModel, + + #[error("Invalid pre or post accounts")] + InvalidPreOrPostAccounts, + + #[error("PoH record error: {0}")] + PohRecordError(String), + + #[error("Tip payment error: {0}")] + TipError(String), + + #[error("A transaction in the bundle failed to execute: [signature={0}, error={1}]")] + TransactionFailure(Signature, String), +} + +impl From for RpcBundleExecutionError { + fn from(bundle_execution_error: BundleExecutionError) -> Self { + match bundle_execution_error { + BundleExecutionError::BankProcessingTimeLimitReached => { + Self::BankProcessingTimeLimitReached + } + BundleExecutionError::ExceedsCostModel => Self::ExceedsCostModel, + BundleExecutionError::TransactionFailure(load_and_execute_bundle_error) => { + match load_and_execute_bundle_error { + LoadAndExecuteBundleError::ProcessingTimeExceeded(_) => { + Self::BundleExecutionTimeout + } + LoadAndExecuteBundleError::LockError { + signature, + transaction_error, + } => Self::TransactionFailure(signature, transaction_error.to_string()), + LoadAndExecuteBundleError::TransactionError { + signature, + execution_result, + } => match *execution_result { + TransactionExecutionResult::Executed { details, .. } => { + let err_msg = if let Err(e) = details.status { + e.to_string() + } else { + "Unknown error".to_string() + }; + Self::TransactionFailure(signature, err_msg) + } + TransactionExecutionResult::NotExecuted(e) => { + Self::TransactionFailure(signature, e.to_string()) + } + }, + LoadAndExecuteBundleError::InvalidPreOrPostAccounts => { + Self::InvalidPreOrPostAccounts + } + } + } + BundleExecutionError::LockError => Self::BundleLockError, + BundleExecutionError::PohRecordError(e) => Self::PohRecordError(e.to_string()), + BundleExecutionError::TipError(e) => Self::TipError(e.to_string()), + } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(rename_all = "camelCase")] +pub struct RpcSimulateBundleResult { + pub summary: RpcBundleSimulationSummary, + pub transaction_results: Vec, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(rename_all = "camelCase")] +pub struct RpcSimulateBundleTransactionResult { + pub err: Option, + pub logs: Option>, + pub pre_execution_accounts: Option>, + pub post_execution_accounts: Option>, + pub units_consumed: Option, + pub return_data: Option, +} + +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RpcSimulateBundleConfig { + /// Gives the state of accounts pre/post transaction execution. + /// The length of each of these must be equal to the number transactions. + pub pre_execution_accounts_configs: Vec>, + pub post_execution_accounts_configs: Vec>, + + /// Specifies the encoding scheme of the contained transactions. + pub transaction_encoding: Option, + + /// Specifies the bank to run simulation against. + pub simulation_bank: Option, + + /// Opt to skip sig-verify for faster performance. + #[serde(default)] + pub skip_sig_verify: bool, + + /// Replace recent blockhash to simulate old transactions without resigning. + #[serde(default)] + pub replace_recent_blockhash: bool, +} + +#[derive(Serialize, Deserialize, Clone, Copy, Debug)] +#[serde(rename_all = "camelCase")] +pub enum SimulationSlotConfig { + /// Simulate on top of bank with the provided commitment. + Commitment(CommitmentConfig), + + /// Simulate on the provided slot's bank. + Slot(Slot), + + /// Simulates on top of the RPC's highest slot's bank i.e. the working bank. + Tip, +} + +impl Default for SimulationSlotConfig { + fn default() -> Self { + Self::Commitment(CommitmentConfig { + commitment: CommitmentLevel::Confirmed, + }) + } +} + +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RpcBundleRequest { + pub encoded_transactions: Vec, +} diff --git a/rpc-client-api/src/config.rs b/rpc-client-api/src/config.rs index bff9c1444a..d16df2d165 100644 --- a/rpc-client-api/src/config.rs +++ b/rpc-client-api/src/config.rs @@ -46,56 +46,6 @@ pub struct RpcSimulateTransactionConfig { pub min_context_slot: Option, } -#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, PartialEq)] -#[serde(rename_all = "camelCase")] -pub enum SimulationSlotConfig { - /// Simulate on top of bank with the provided commitment. - Commitment(CommitmentConfig), - - /// Simulate on the provided slot's bank. - Slot(Slot), - - /// Simulates on top of the RPC's highest slot's bank i.e. the working bank. - Tip, -} - -impl Default for SimulationSlotConfig { - fn default() -> Self { - Self::Commitment(CommitmentConfig { - commitment: CommitmentLevel::Confirmed, - }) - } -} - -#[derive(Debug, PartialEq, Default, Eq, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct RpcBundleRequest { - pub encoded_transactions: Vec, -} - -#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct RpcSimulateBundleConfig { - /// Gives the state of accounts pre/post transaction execution. - /// The length of each of these must be equal to the number transactions. - pub pre_execution_accounts_configs: Vec>, - pub post_execution_accounts_configs: Vec>, - - /// Specifies the encoding scheme of the contained transactions. - pub transaction_encoding: Option, - - /// Specifies the bank to run simulation against. - pub simulation_bank: Option, - - /// Opt to skip sig-verify for faster performance. - #[serde(default)] - pub skip_sig_verify: bool, - - /// Replace recent blockhash to simulate old transactions without resigning. - #[serde(default)] - pub replace_recent_blockhash: bool, -} - #[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RpcRequestAirdropConfig { diff --git a/rpc-client-api/src/lib.rs b/rpc-client-api/src/lib.rs index 9be15cbab4..1d8b1e2dd1 100644 --- a/rpc-client-api/src/lib.rs +++ b/rpc-client-api/src/lib.rs @@ -1,5 +1,6 @@ #![allow(clippy::integer_arithmetic)] +pub mod bundles; pub mod client_error; pub mod config; pub mod custom_error; diff --git a/rpc-client-api/src/response.rs b/rpc-client-api/src/response.rs index cb1168fcc6..e20b92d1fc 100644 --- a/rpc-client-api/src/response.rs +++ b/rpc-client-api/src/response.rs @@ -377,24 +377,6 @@ pub struct RpcIdentity { pub identity: String, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub enum RpcBundleSimulationSummary { - /// error and offending transaction signature - Failed { - error: (), - tx_signature: String, - }, - Succeeded, -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -#[serde(rename_all = "camelCase")] -pub struct RpcSimulateBundleResult { - pub summary: RpcBundleSimulationSummary, - pub transaction_results: Vec, -} - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct RpcVote { @@ -449,18 +431,6 @@ pub struct RpcSignatureConfirmation { pub status: Result<()>, } -// TODO: consolidate with [RpcSimulateTransactionResult] -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct RpcSimulateBundleTransactionResult { - pub err: Option, - pub logs: Option>, - pub pre_execution_accounts: Option>, - pub post_execution_accounts: Option>, - pub units_consumed: Option, - pub return_data: Option, -} - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct RpcSimulateTransactionResult { diff --git a/rpc-client/src/nonblocking/rpc_client.rs b/rpc-client/src/nonblocking/rpc_client.rs index cc6a74c337..145c3417e0 100644 --- a/rpc-client/src/nonblocking/rpc_client.rs +++ b/rpc-client/src/nonblocking/rpc_client.rs @@ -33,6 +33,10 @@ use { UiAccount, UiAccountData, UiAccountEncoding, }, solana_rpc_client_api::{ + bundles::{ + RpcBundleRequest, RpcSimulateBundleConfig, RpcSimulateBundleResult, + SimulationSlotConfig, + }, client_error::{ Error as ClientError, ErrorKind as ClientErrorKind, Result as ClientResult, }, diff --git a/rpc-client/src/rpc_client.rs b/rpc-client/src/rpc_client.rs index 61e6fd54bc..33301ea73c 100644 --- a/rpc-client/src/rpc_client.rs +++ b/rpc-client/src/rpc_client.rs @@ -28,6 +28,7 @@ use { UiAccount, UiAccountEncoding, }, solana_rpc_client_api::{ + bundles::{RpcSimulateBundleConfig, RpcSimulateBundleResult}, client_error::{Error as ClientError, ErrorKind, Result as ClientResult}, config::{RpcAccountInfoConfig, *}, request::{RpcRequest, TokenAccountsFilter}, diff --git a/rpc/src/rpc.rs b/rpc/src/rpc.rs index 2dcd6bc9d0..4659fbf58f 100644 --- a/rpc/src/rpc.rs +++ b/rpc/src/rpc.rs @@ -3272,13 +3272,16 @@ pub mod utils { crate::rpc::encode_account, jsonrpc_core::Error, solana_account_decoder::{UiAccount, UiAccountEncoding}, - solana_bundle::bundle_execution::{LoadAndExecuteBundleError, LoadAndExecuteBundleOutput}, + solana_bundle::{ + bundle_execution::{LoadAndExecuteBundleError, LoadAndExecuteBundleOutput}, + BundleExecutionError, + }, solana_rpc_client_api::{ - config::{RpcSimulateBundleConfig, RpcSimulateTransactionAccountsConfig}, - response::{ - RpcBundleSimulationSummary, RpcSimulateBundleResult, - RpcSimulateBundleTransactionResult, + bundles::{ + RpcBundleExecutionError, RpcBundleSimulationSummary, RpcSimulateBundleConfig, + RpcSimulateBundleResult, RpcSimulateBundleTransactionResult, }, + config::RpcSimulateTransactionAccountsConfig, }, solana_sdk::{account::AccountSharedData, pubkey::Pubkey}, std::str::FromStr, @@ -3302,26 +3305,27 @@ pub mod utils { } } - // create a [RpcSimulateBundleResult] from a given bank [BundleSimulationResult] pub fn rpc_bundle_result_from_bank_result( bundle_execution_result: LoadAndExecuteBundleOutput, rpc_config: RpcSimulateBundleConfig, ) -> Result { let summary = match bundle_execution_result.result() { Ok(_) => RpcBundleSimulationSummary::Succeeded, - Err(LoadAndExecuteBundleError::LockError { .. }) - | Err(LoadAndExecuteBundleError::ProcessingTimeExceeded(_)) - | Err(LoadAndExecuteBundleError::InvalidPreOrPostAccounts) => { - // shouldn't ever hit this case bc caller checks against these errors - unreachable!(); + Err(e) => { + let tx_signature = match e { + LoadAndExecuteBundleError::TransactionError { signature, .. } + | LoadAndExecuteBundleError::LockError { signature, .. } => { + Some(signature.to_string()) + } + _ => None, + }; + RpcBundleSimulationSummary::Failed { + error: RpcBundleExecutionError::from(BundleExecutionError::TransactionFailure( + e.clone(), + )), + tx_signature, + } } - Err(LoadAndExecuteBundleError::TransactionError { - signature, - execution_result: _, // TODO (LB): can attach results here - }) => RpcBundleSimulationSummary::Failed { - error: (), - tx_signature: signature.to_string(), - }, }; let mut transaction_results = Vec::new(); @@ -3339,15 +3343,16 @@ pub mod utils { let account_config = rpc_config .pre_execution_accounts_configs .get(transaction_results.len()) - .ok_or_else(|| Error::invalid_params("the length of pre_execution_accounts_configs must match the number of transactions"))?.as_ref().ok_or_else(|| Error::invalid_params("the length of pre_execution_accounts_configs must match the number of transactions"))?; + .ok_or_else(|| Error::invalid_params("the length of pre_execution_accounts_configs must match the number of transactions"))?; + let account_encoding = account_config + .as_ref() + .and_then(|config| config.encoding) + .unwrap_or(UiAccountEncoding::Base64); let pre_execution_accounts = if let Some(pre_tx_accounts) = bundle_output.pre_tx_execution_accounts().get(index) { - try_encode_accounts( - pre_tx_accounts, - account_config.encoding.unwrap_or(UiAccountEncoding::Base64), - )? + try_encode_accounts(pre_tx_accounts, account_encoding)? } else { None }; @@ -3355,10 +3360,7 @@ pub mod utils { let post_execution_accounts = if let Some(post_tx_accounts) = bundle_output.post_tx_execution_accounts().get(index) { - try_encode_accounts( - post_tx_accounts, - account_config.encoding.unwrap_or(UiAccountEncoding::Base64), - )? + try_encode_accounts(post_tx_accounts, account_encoding)? } else { None }; @@ -3416,6 +3418,10 @@ pub mod rpc_full { crate::rpc::utils::{account_configs_to_accounts, rpc_bundle_result_from_bank_result}, jsonrpc_core::ErrorCode, solana_bundle::bundle_execution::{load_and_execute_bundle, LoadAndExecuteBundleError}, + solana_rpc_client_api::bundles::{ + RpcBundleRequest, RpcSimulateBundleConfig, RpcSimulateBundleResult, + SimulationSlotConfig, + }, solana_sdk::{ bundle::{derive_bundle_id, SanitizedBundle}, clock::MAX_PROCESSING_AGE, diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index de3a2f3ce8..4c67193937 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -401,29 +401,12 @@ impl DurableNonceFee { } } -#[derive(Debug, Clone, PartialEq)] -pub enum BundleSimulationSummary { - // error and transaction signature responsible - Failed { - error: (), //BundleExecutionError, - tx_signature: Signature, - }, - Succeeded, -} - #[derive(Clone, Debug, PartialEq)] pub struct AccountData { pub pubkey: Pubkey, pub data: AccountSharedData, } -#[derive(Clone)] -pub struct BundleSimulationResult { - /// Gives high level summary of bundle. - pub summary: (), //BundleSimulationSummary, - pub transaction_results: Vec, -} - #[derive(Clone)] pub struct BundleTransactionSimulationResult { pub result: Result<()>, diff --git a/sdk/src/bundle/mod.rs b/sdk/src/bundle/mod.rs index 9c45736cd6..3c02a59f9f 100644 --- a/sdk/src/bundle/mod.rs +++ b/sdk/src/bundle/mod.rs @@ -24,7 +24,7 @@ pub fn derive_bundle_id(transactions: &[VersionedTransaction]) -> String { format!("{:x}", hasher.finalize()) } -pub fn derive_bundle_id_from_sanizited_transactions( +pub fn derive_bundle_id_from_sanitized_transactions( transactions: &[SanitizedTransaction], ) -> String { let mut hasher = Sha256::new();