Skip to content
This repository was archived by the owner on Jan 22, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions banks-client/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,21 @@ pub enum BanksClientError {

#[error("transport transaction error: {0}")]
TransactionError(#[from] TransactionError),

#[error("simulation error: {err:?}, logs: {logs:?}, units_consumed: {units_consumed:?}")]
SimulationError {
err: TransactionError,
logs: Vec<String>,
units_consumed: u64,
},
}

impl BanksClientError {
pub fn unwrap(&self) -> TransactionError {
if let BanksClientError::TransactionError(err) = self {
err.clone()
} else {
panic!("unexpected transport error")
match self {
BanksClientError::TransactionError(err)
| BanksClientError::SimulationError { err, .. } => err.clone(),
_ => panic!("unexpected transport error"),
}
}
}
Expand All @@ -40,6 +47,9 @@ impl From<BanksClientError> for io::Error {
BanksClientError::TransactionError(err) => {
Self::new(io::ErrorKind::Other, err.to_string())
}
BanksClientError::SimulationError { err, .. } => {
Self::new(io::ErrorKind::Other, err.to_string())
}
}
}
}
Expand All @@ -57,6 +67,7 @@ impl From<BanksClientError> for TransportError {
Self::IoError(io::Error::new(io::ErrorKind::Other, err.to_string()))
}
BanksClientError::TransactionError(err) => Self::TransactionError(err),
BanksClientError::SimulationError { err, .. } => Self::TransactionError(err),
}
}
}
66 changes: 65 additions & 1 deletion banks-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use {
crate::error::BanksClientError,
borsh::BorshDeserialize,
futures::{future::join_all, Future, FutureExt, TryFutureExt},
solana_banks_interface::{BanksRequest, BanksResponse},
solana_banks_interface::{BanksRequest, BanksResponse, BanksTransactionResultWithSimulation},
solana_program::{
clock::Slot, fee_calculator::FeeCalculator, hash::Hash, program_pack::Pack, pubkey::Pubkey,
rent::Rent, sysvar::Sysvar,
Expand Down Expand Up @@ -128,6 +128,22 @@ impl BanksClient {
.map_err(Into::into)
}

pub fn process_transaction_with_preflight_and_commitment_and_context(
&mut self,
ctx: Context,
transaction: Transaction,
commitment: CommitmentLevel,
) -> impl Future<Output = Result<BanksTransactionResultWithSimulation, BanksClientError>> + '_
{
self.inner
.process_transaction_with_preflight_and_commitment_and_context(
ctx,
transaction,
commitment,
)
.map_err(Into::into)
}

pub fn get_account_with_commitment_and_context(
&mut self,
ctx: Context,
Expand Down Expand Up @@ -211,6 +227,54 @@ impl BanksClient {
.map_err(Into::into) // Remove this when return Err type updated to BanksClientError
}

/// Send a transaction and return any preflight (sanitization or simulation) errors, or return
/// after the transaction has been rejected or reached the given level of commitment.
pub fn process_transaction_with_preflight_and_commitment(
&mut self,
transaction: Transaction,
commitment: CommitmentLevel,
) -> impl Future<Output = Result<(), BanksClientError>> + '_ {
let mut ctx = context::current();
ctx.deadline += Duration::from_secs(50);
self.process_transaction_with_preflight_and_commitment_and_context(
ctx,
transaction,
commitment,
)
.map(|result| match result? {
BanksTransactionResultWithSimulation {
result: None,
simulation_details: _,
} => Err(BanksClientError::ClientError(
"invalid blockhash or fee-payer",
)),
BanksTransactionResultWithSimulation {
result: Some(Err(err)),
simulation_details: Some(simulation_details),
} => Err(BanksClientError::SimulationError {
err,
logs: simulation_details.logs,
units_consumed: simulation_details.units_consumed,
}),
BanksTransactionResultWithSimulation {
result: Some(result),
simulation_details: _,
} => result.map_err(Into::into),
})
}

/// Send a transaction and return any preflight (sanitization or simulation) errors, or return
/// after the transaction has been finalized or rejected.
pub fn process_transaction_with_preflight(
&mut self,
transaction: Transaction,
) -> impl Future<Output = Result<(), BanksClientError>> + '_ {
self.process_transaction_with_preflight_and_commitment(
transaction,
CommitmentLevel::default(),
)
}

/// Send a transaction and return until the transaction has been finalized or rejected.
pub fn process_transaction(
&mut self,
Expand Down
17 changes: 17 additions & 0 deletions banks-interface/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,19 @@ pub struct TransactionStatus {
pub confirmation_status: Option<TransactionConfirmationStatus>,
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TransactionSimulationDetails {
pub logs: Vec<String>,
pub units_consumed: u64,
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct BanksTransactionResultWithSimulation {
pub result: Option<transaction::Result<()>>,
pub simulation_details: Option<TransactionSimulationDetails>,
}

#[tarpc::service]
pub trait Banks {
async fn send_transaction_with_context(transaction: Transaction);
Expand All @@ -44,6 +57,10 @@ pub trait Banks {
-> Option<TransactionStatus>;
async fn get_slot_with_context(commitment: CommitmentLevel) -> Slot;
async fn get_block_height_with_context(commitment: CommitmentLevel) -> u64;
async fn process_transaction_with_preflight_and_commitment_and_context(
transaction: Transaction,
commitment: CommitmentLevel,
) -> BanksTransactionResultWithSimulation;
async fn process_transaction_with_commitment_and_context(
transaction: Transaction,
commitment: CommitmentLevel,
Expand Down
52 changes: 49 additions & 3 deletions banks-server/src/banks_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@ use {
bincode::{deserialize, serialize},
futures::{future, prelude::stream::StreamExt},
solana_banks_interface::{
Banks, BanksRequest, BanksResponse, TransactionConfirmationStatus, TransactionStatus,
Banks, BanksRequest, BanksResponse, BanksTransactionResultWithSimulation,
TransactionConfirmationStatus, TransactionSimulationDetails, TransactionStatus,
},
solana_runtime::{
bank::{Bank, TransactionSimulationResult},
bank_forks::BankForks,
commitment::BlockCommitmentCache,
},
solana_runtime::{bank::Bank, bank_forks::BankForks, commitment::BlockCommitmentCache},
solana_sdk::{
account::Account,
clock::Slot,
Expand All @@ -15,7 +20,7 @@ use {
message::{Message, SanitizedMessage},
pubkey::Pubkey,
signature::Signature,
transaction::{self, Transaction},
transaction::{self, SanitizedTransaction, Transaction},
},
solana_send_transaction_service::{
send_transaction_service::{SendTransactionService, TransactionInfo},
Expand Down Expand Up @@ -242,6 +247,47 @@ impl Banks for BanksServer {
self.bank(commitment).block_height()
}

async fn process_transaction_with_preflight_and_commitment_and_context(
self,
ctx: Context,
transaction: Transaction,
commitment: CommitmentLevel,
) -> BanksTransactionResultWithSimulation {
let sanitized_transaction =
match SanitizedTransaction::try_from_legacy_transaction(transaction.clone()) {
Err(err) => {
return BanksTransactionResultWithSimulation {
result: Some(Err(err)),
simulation_details: None,
};
}
Ok(tx) => tx,
};
if let TransactionSimulationResult {
result: Err(err),
logs,
post_simulation_accounts: _,
units_consumed,
} = self
.bank(commitment)
.simulate_transaction_unchecked(sanitized_transaction)
{
return BanksTransactionResultWithSimulation {
result: Some(Err(err)),
simulation_details: Some(TransactionSimulationDetails {
logs,
units_consumed,
}),
};
}
BanksTransactionResultWithSimulation {
result: self
.process_transaction_with_commitment_and_context(ctx, transaction, commitment)
.await,
simulation_details: None,
}
}

async fn process_transaction_with_commitment_and_context(
self,
_: Context,
Expand Down
9 changes: 9 additions & 0 deletions runtime/src/bank.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3119,6 +3119,15 @@ impl Bank {
) -> TransactionSimulationResult {
assert!(self.is_frozen(), "simulation bank must be frozen");

self.simulate_transaction_unchecked(transaction)
}

/// Run transactions against a bank without committing the results; does not check if the bank
/// is frozen, enabling use in single-Bank test frameworks
pub fn simulate_transaction_unchecked(
&self,
transaction: SanitizedTransaction,
) -> TransactionSimulationResult {
let number_of_accounts = transaction.message().account_keys_len();
let batch = self.prepare_simulation_batch(transaction);
let mut timings = ExecuteTimings::default();
Expand Down
16 changes: 10 additions & 6 deletions sdk/src/transaction/sanitized.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,20 +76,24 @@ impl SanitizedTransaction {
})
}

/// Create a sanitized transaction from a legacy transaction. Used for tests only.
pub fn from_transaction_for_tests(tx: Transaction) -> Self {
tx.sanitize().unwrap();
pub fn try_from_legacy_transaction(tx: Transaction) -> Result<Self> {
tx.sanitize()?;

if tx.message.has_duplicates() {
Result::<Self>::Err(TransactionError::AccountLoadedTwice).unwrap();
return Err(TransactionError::AccountLoadedTwice);
}

Self {
Ok(Self {
message_hash: tx.message.hash(),
message: SanitizedMessage::Legacy(tx.message),
is_simple_vote_tx: false,
signatures: tx.signatures,
}
})
}

/// Create a sanitized transaction from a legacy transaction. Used for tests only.
pub fn from_transaction_for_tests(tx: Transaction) -> Self {
Self::try_from_legacy_transaction(tx).unwrap()
}

/// Return the first signature for this transaction.
Expand Down