From 7b5e8d290dbd98c81504b8bdee8bbd5a7de2f8bc Mon Sep 17 00:00:00 2001 From: segfaultdoctor <17258903+segfaultdoc@users.noreply.github.com> Date: Tue, 10 Oct 2023 18:28:46 -0400 Subject: [PATCH] improve simulate_bundle errors and response (#404) --- Cargo.lock | 5 + bundle/Cargo.toml | 3 + bundle/src/bundle_execution.rs | 22 ++- bundle/src/lib.rs | 59 +++++++ ci/check-crates.sh | 3 + core/Cargo.toml | 1 + .../unprocessed_transaction_storage.rs | 18 +- core/src/bundle_stage.rs | 1 - core/src/bundle_stage/bundle_consumer.rs | 24 +-- .../bundle_stage/bundle_packet_receiver.rs | 23 +-- .../bundle_stage_leader_metrics.rs | 15 +- core/src/tip_manager.rs | 30 +--- programs/sbf/Cargo.lock | 5 + rpc-client-api/Cargo.toml | 2 + rpc-client-api/src/bundles.rs | 166 ++++++++++++++++++ rpc-client-api/src/config.rs | 50 ------ rpc-client-api/src/lib.rs | 1 + rpc-client-api/src/response.rs | 30 ---- rpc-client/src/nonblocking/rpc_client.rs | 4 + rpc-client/src/rpc_client.rs | 1 + rpc/src/rpc.rs | 60 ++++--- runtime/src/bank.rs | 17 -- sdk/src/bundle/mod.rs | 2 +- 23 files changed, 343 insertions(+), 199 deletions(-) create mode 100644 rpc-client-api/src/bundles.rs diff --git a/Cargo.lock b/Cargo.lock index 0057229e0b..cdc6eae453 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5849,13 +5849,16 @@ dependencies = [ name = "solana-bundle" version = "1.18.0" dependencies = [ + "anchor-lang", "assert_matches", "itertools", "log", + "serde", "solana-accounts-db", "solana-ledger", "solana-logger", "solana-measure", + "solana-poh", "solana-program-runtime", "solana-runtime", "solana-sdk", @@ -7246,6 +7249,8 @@ dependencies = [ "serde_derive", "serde_json", "solana-account-decoder", + "solana-accounts-db", + "solana-bundle", "solana-sdk", "solana-transaction-status", "solana-version", diff --git a/bundle/Cargo.toml b/bundle/Cargo.toml index 1aa2987c2c..babb13bcd7 100644 --- a/bundle/Cargo.toml +++ b/bundle/Cargo.toml @@ -11,12 +11,15 @@ license = { workspace = true } edition = { workspace = true } [dependencies] +anchor-lang = { workspace = true } itertools = { workspace = true } log = { workspace = true } +serde = { workspace = true } solana-accounts-db = { 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 b4bf02e986..e69096f8ed 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_accounts_db::{ account_overrides::AccountOverrides, accounts::TransactionLoadResult, transaction_results::TransactionExecutionResult, @@ -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 f1d57c85b2..e3377370e6 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -96,6 +96,7 @@ serde_json = { workspace = true } serial_test = { workspace = true } solana-accounts-db = { workspace = true } # See order-crates-for-publishing.py for using this unusual `path = "."` +solana-bundle = { workspace = true } solana-core = { path = ".", features = ["dev-context-only-utils"] } solana-logger = { workspace = true } solana-program-runtime = { workspace = true } diff --git a/core/src/banking_stage/unprocessed_transaction_storage.rs b/core/src/banking_stage/unprocessed_transaction_storage.rs index 94201da9ce..63f898a593 100644 --- a/core/src/banking_stage/unprocessed_transaction_storage.rs +++ b/core/src/banking_stage/unprocessed_transaction_storage.rs @@ -15,14 +15,13 @@ use { BankingStageStats, FilterForwardingResults, ForwardOption, }, crate::{ - bundle_stage::{ - bundle_stage_leader_metrics::BundleStageLeaderMetrics, result::BundleExecutionError, - }, + bundle_stage::bundle_stage_leader_metrics::BundleStageLeaderMetrics, immutable_deserialized_bundle::ImmutableDeserializedBundle, }, itertools::Itertools, min_max_heap::MinMaxHeap, solana_accounts_db::transaction_error_metrics::TransactionErrorMetrics, + solana_bundle::BundleExecutionError, solana_measure::{measure, measure_us}, solana_runtime::bank::Bank, solana_sdk::{ @@ -1233,19 +1232,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 @@ -1262,7 +1264,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/core/src/bundle_stage.rs b/core/src/bundle_stage.rs index dd6ed918f1..3a4103831b 100644 --- a/core/src/bundle_stage.rs +++ b/core/src/bundle_stage.rs @@ -44,7 +44,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 725c41a2ba..bbabc77d9c 100644 --- a/core/src/bundle_stage/bundle_consumer.rs +++ b/core/src/bundle_stage/bundle_consumer.rs @@ -10,15 +10,17 @@ use { bundle_reserved_space_manager::BundleReservedSpaceManager, bundle_stage_leader_metrics::BundleStageLeaderMetrics, committer::Committer, - result::{BundleExecutionError, BundleExecutionResult}, }, consensus_cache_updater::ConsensusCacheUpdater, immutable_deserialized_bundle::ImmutableDeserializedBundle, proxy::block_engine_stage::BlockBuilderFeeInfo, - tip_manager::{TipManager, TipPaymentError}, + tip_manager::TipManager, }, solana_accounts_db::transaction_error_metrics::TransactionErrorMetrics, - solana_bundle::bundle_execution::{load_and_execute_bundle, BundleExecutionMetrics}, + solana_bundle::{ + bundle_execution::{load_and_execute_bundle, BundleExecutionMetrics}, + BundleExecutionError, BundleExecutionResult, TipError, + }, solana_cost_model::transaction_cost::TransactionCost, solana_gossip::cluster_info::ClusterInfo, solana_measure::{measure, measure_us}, @@ -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 f53dc274d0..f67f8f946d 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 5e09ebe78d..52c1aa0714 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::{ banking_stage::{leader_slot_metrics, leader_slot_metrics::LeaderSlotMetricsTracker}, - bundle_stage::result::BundleExecutionError, immutable_deserialized_bundle::DeserializedBundleError, }, - 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/programs/sbf/Cargo.lock b/programs/sbf/Cargo.lock index 2fa2499fae..57b84c7c4f 100644 --- a/programs/sbf/Cargo.lock +++ b/programs/sbf/Cargo.lock @@ -4898,12 +4898,15 @@ dependencies = [ name = "solana-bundle" version = "1.18.0" dependencies = [ + "anchor-lang", "itertools", "log", + "serde", "solana-accounts-db", "solana-ledger", "solana-logger", "solana-measure", + "solana-poh", "solana-program-runtime", "solana-runtime", "solana-sdk", @@ -5775,6 +5778,8 @@ dependencies = [ "serde_derive", "serde_json", "solana-account-decoder", + "solana-accounts-db", + "solana-bundle", "solana-sdk", "solana-transaction-status", "solana-version", diff --git a/rpc-client-api/Cargo.toml b/rpc-client-api/Cargo.toml index 92bc9d4958..1ebc304544 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-accounts-db = { workspace = true } +solana-bundle = { 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..72afc51d91 --- /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_accounts_db::transaction_results::TransactionExecutionResult, + solana_bundle::{bundle_execution::LoadAndExecuteBundleError, BundleExecutionError}, + 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 6386a433f7..a8c01769a4 100644 --- a/rpc-client-api/src/lib.rs +++ b/rpc-client-api/src/lib.rs @@ -1,5 +1,6 @@ #![allow(clippy::arithmetic_side_effects)] +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 445ba7fe11..3b048e384b 100644 --- a/rpc/src/rpc.rs +++ b/rpc/src/rpc.rs @@ -3268,13 +3268,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, @@ -3298,26 +3301,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(); @@ -3335,15 +3339,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 }; @@ -3351,10 +3356,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 }; @@ -3412,6 +3414,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 801aaeffa4..1d841e6a0a 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -331,29 +331,12 @@ pub struct LoadAndExecuteTransactionsOutput { pub error_counters: TransactionErrorMetrics, } -#[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();