Support versioned transactions in program test framework (#28739)

* Support versioned transactions in program test framework

* use working bank

* Update to process_transaction_with_metadata

* Migrate client apis from Transaction to Into<VersionedTransaction>

* feedback
This commit is contained in:
Justin Starry 2022-11-18 23:43:52 +08:00 committed by GitHub
parent f1e7ffba0c
commit 7371608722
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 153 additions and 52 deletions

1
Cargo.lock generated
View File

@ -5919,6 +5919,7 @@ dependencies = [
"log",
"serde",
"solana-banks-client",
"solana-banks-interface",
"solana-banks-server",
"solana-bpf-loader-program",
"solana-logger 1.15.0",

View File

@ -12,7 +12,10 @@ pub use {
use {
borsh::BorshDeserialize,
futures::{future::join_all, Future, FutureExt, TryFutureExt},
solana_banks_interface::{BanksRequest, BanksResponse, BanksTransactionResultWithSimulation},
solana_banks_interface::{
BanksRequest, BanksResponse, BanksTransactionResultWithMetadata,
BanksTransactionResultWithSimulation,
},
solana_program::{
clock::Slot, fee_calculator::FeeCalculator, hash::Hash, program_pack::Pack, pubkey::Pubkey,
rent::Rent, sysvar::Sysvar,
@ -22,7 +25,7 @@ use {
commitment_config::CommitmentLevel,
message::Message,
signature::Signature,
transaction::{self, Transaction},
transaction::{self, Transaction, VersionedTransaction},
},
tarpc::{
client::{self, NewClient, RequestDispatch},
@ -59,10 +62,10 @@ impl BanksClient {
pub fn send_transaction_with_context(
&mut self,
ctx: Context,
transaction: Transaction,
transaction: impl Into<VersionedTransaction>,
) -> impl Future<Output = Result<(), BanksClientError>> + '_ {
self.inner
.send_transaction_with_context(ctx, transaction)
.send_transaction_with_context(ctx, transaction.into())
.map_err(Into::into)
}
@ -114,39 +117,50 @@ impl BanksClient {
pub fn process_transaction_with_commitment_and_context(
&mut self,
ctx: Context,
transaction: Transaction,
transaction: impl Into<VersionedTransaction>,
commitment: CommitmentLevel,
) -> impl Future<Output = Result<Option<transaction::Result<()>>, BanksClientError>> + '_ {
self.inner
.process_transaction_with_commitment_and_context(ctx, transaction, commitment)
.process_transaction_with_commitment_and_context(ctx, transaction.into(), commitment)
.map_err(Into::into)
}
pub fn process_transaction_with_preflight_and_commitment_and_context(
&mut self,
ctx: Context,
transaction: Transaction,
transaction: impl Into<VersionedTransaction>,
commitment: CommitmentLevel,
) -> impl Future<Output = Result<BanksTransactionResultWithSimulation, BanksClientError>> + '_
{
self.inner
.process_transaction_with_preflight_and_commitment_and_context(
ctx,
transaction,
transaction.into(),
commitment,
)
.map_err(Into::into)
}
pub fn process_transaction_with_metadata_and_context(
&mut self,
ctx: Context,
transaction: impl Into<VersionedTransaction>,
) -> impl Future<Output = Result<BanksTransactionResultWithMetadata, BanksClientError>> + '_
{
self.inner
.process_transaction_with_metadata_and_context(ctx, transaction.into())
.map_err(Into::into)
}
pub fn simulate_transaction_with_commitment_and_context(
&mut self,
ctx: Context,
transaction: Transaction,
transaction: impl Into<VersionedTransaction>,
commitment: CommitmentLevel,
) -> impl Future<Output = Result<BanksTransactionResultWithSimulation, BanksClientError>> + '_
{
self.inner
.simulate_transaction_with_commitment_and_context(ctx, transaction, commitment)
.simulate_transaction_with_commitment_and_context(ctx, transaction.into(), commitment)
.map_err(Into::into)
}
@ -166,9 +180,9 @@ impl BanksClient {
/// blockhash expires.
pub fn send_transaction(
&mut self,
transaction: Transaction,
transaction: impl Into<VersionedTransaction>,
) -> impl Future<Output = Result<(), BanksClientError>> + '_ {
self.send_transaction_with_context(context::current(), transaction)
self.send_transaction_with_context(context::current(), transaction.into())
}
/// Return the fee parameters associated with a recent, rooted blockhash. The cluster
@ -231,6 +245,17 @@ impl BanksClient {
})
}
/// Process a transaction and return the result with metadata.
pub fn process_transaction_with_metadata(
&mut self,
transaction: impl Into<VersionedTransaction>,
) -> impl Future<Output = Result<BanksTransactionResultWithMetadata, BanksClientError>> + '_
{
let mut ctx = context::current();
ctx.deadline += Duration::from_secs(50);
self.process_transaction_with_metadata_and_context(ctx, transaction.into())
}
/// 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(

View File

@ -11,7 +11,7 @@ use {
message::Message,
pubkey::Pubkey,
signature::Signature,
transaction::{self, Transaction, TransactionError},
transaction::{self, TransactionError, VersionedTransaction},
transaction_context::TransactionReturnData,
},
};
@ -39,15 +39,29 @@ pub struct TransactionSimulationDetails {
pub return_data: Option<TransactionReturnData>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TransactionMetadata {
pub log_messages: Vec<String>,
pub compute_units_consumed: u64,
pub return_data: Option<TransactionReturnData>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct BanksTransactionResultWithSimulation {
pub result: Option<transaction::Result<()>>,
pub simulation_details: Option<TransactionSimulationDetails>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct BanksTransactionResultWithMetadata {
pub result: transaction::Result<()>,
pub metadata: Option<TransactionMetadata>,
}
#[tarpc::service]
pub trait Banks {
async fn send_transaction_with_context(transaction: Transaction);
async fn send_transaction_with_context(transaction: VersionedTransaction);
#[deprecated(
since = "1.9.0",
note = "Please use `get_fee_for_message_with_commitment_and_context` instead"
@ -60,15 +74,18 @@ pub trait Banks {
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,
transaction: VersionedTransaction,
commitment: CommitmentLevel,
) -> BanksTransactionResultWithSimulation;
async fn process_transaction_with_commitment_and_context(
transaction: Transaction,
transaction: VersionedTransaction,
commitment: CommitmentLevel,
) -> Option<transaction::Result<()>>;
async fn process_transaction_with_metadata_and_context(
transaction: VersionedTransaction,
) -> BanksTransactionResultWithMetadata;
async fn simulate_transaction_with_commitment_and_context(
transaction: Transaction,
transaction: VersionedTransaction,
commitment: CommitmentLevel,
) -> BanksTransactionResultWithSimulation;
async fn get_account_with_commitment_and_context(

View File

@ -3,11 +3,12 @@ use {
crossbeam_channel::{unbounded, Receiver, Sender},
futures::{future, prelude::stream::StreamExt},
solana_banks_interface::{
Banks, BanksRequest, BanksResponse, BanksTransactionResultWithSimulation,
TransactionConfirmationStatus, TransactionSimulationDetails, TransactionStatus,
Banks, BanksRequest, BanksResponse, BanksTransactionResultWithMetadata,
BanksTransactionResultWithSimulation, TransactionConfirmationStatus, TransactionMetadata,
TransactionSimulationDetails, TransactionStatus,
},
solana_runtime::{
bank::{Bank, TransactionSimulationResult},
bank::{Bank, TransactionExecutionResult, TransactionSimulationResult},
bank_forks::BankForks,
commitment::BlockCommitmentCache,
},
@ -21,7 +22,7 @@ use {
message::{Message, SanitizedMessage},
pubkey::Pubkey,
signature::Signature,
transaction::{self, SanitizedTransaction, Transaction},
transaction::{self, MessageHash, SanitizedTransaction, VersionedTransaction},
},
solana_send_transaction_service::{
send_transaction_service::{SendTransactionService, TransactionInfo},
@ -150,7 +151,7 @@ impl BanksServer {
}
fn verify_transaction(
transaction: &Transaction,
transaction: &SanitizedTransaction,
feature_set: &Arc<FeatureSet>,
) -> transaction::Result<()> {
transaction.verify()?;
@ -160,10 +161,15 @@ fn verify_transaction(
fn simulate_transaction(
bank: &Bank,
transaction: Transaction,
transaction: VersionedTransaction,
) -> BanksTransactionResultWithSimulation {
let sanitized_transaction = match SanitizedTransaction::try_from_legacy_transaction(transaction)
{
let sanitized_transaction = match SanitizedTransaction::try_create(
transaction,
MessageHash::Compute,
Some(false), // is_simple_vote_tx
bank,
true, // require_static_program_ids
) {
Err(err) => {
return BanksTransactionResultWithSimulation {
result: Some(Err(err)),
@ -192,8 +198,8 @@ fn simulate_transaction(
#[tarpc::server]
impl Banks for BanksServer {
async fn send_transaction_with_context(self, _: Context, transaction: Transaction) {
let blockhash = &transaction.message.recent_blockhash;
async fn send_transaction_with_context(self, _: Context, transaction: VersionedTransaction) {
let blockhash = transaction.message.recent_blockhash();
let last_valid_block_height = self
.bank_forks
.read()
@ -278,7 +284,7 @@ impl Banks for BanksServer {
async fn process_transaction_with_preflight_and_commitment_and_context(
self,
ctx: Context,
transaction: Transaction,
transaction: VersionedTransaction,
commitment: CommitmentLevel,
) -> BanksTransactionResultWithSimulation {
let mut simulation_result =
@ -296,7 +302,7 @@ impl Banks for BanksServer {
async fn simulate_transaction_with_commitment_and_context(
self,
_: Context,
transaction: Transaction,
transaction: VersionedTransaction,
commitment: CommitmentLevel,
) -> BanksTransactionResultWithSimulation {
simulate_transaction(&self.bank(commitment), transaction)
@ -305,21 +311,33 @@ impl Banks for BanksServer {
async fn process_transaction_with_commitment_and_context(
self,
_: Context,
transaction: Transaction,
transaction: VersionedTransaction,
commitment: CommitmentLevel,
) -> Option<transaction::Result<()>> {
if let Err(err) = verify_transaction(&transaction, &self.bank(commitment).feature_set) {
let bank = self.bank(commitment);
let sanitized_transaction = match SanitizedTransaction::try_create(
transaction.clone(),
MessageHash::Compute,
Some(false), // is_simple_vote_tx
bank.as_ref(),
true, // require_static_program_ids
) {
Ok(tx) => tx,
Err(err) => return Some(Err(err)),
};
if let Err(err) = verify_transaction(&sanitized_transaction, &bank.feature_set) {
return Some(Err(err));
}
let blockhash = &transaction.message.recent_blockhash;
let blockhash = transaction.message.recent_blockhash();
let last_valid_block_height = self
.bank(commitment)
.get_blockhash_last_valid_block_height(blockhash)
.unwrap();
let signature = transaction.signatures.get(0).cloned().unwrap_or_default();
let signature = sanitized_transaction.signature();
let info = TransactionInfo::new(
signature,
*signature,
serialize(&transaction).unwrap(),
last_valid_block_height,
None,
@ -327,10 +345,34 @@ impl Banks for BanksServer {
None,
);
self.transaction_sender.send(info).unwrap();
self.poll_signature_status(&signature, blockhash, last_valid_block_height, commitment)
self.poll_signature_status(signature, blockhash, last_valid_block_height, commitment)
.await
}
async fn process_transaction_with_metadata_and_context(
self,
_: Context,
transaction: VersionedTransaction,
) -> BanksTransactionResultWithMetadata {
let bank = self.bank_forks.read().unwrap().working_bank();
match bank.process_transaction_with_metadata(transaction) {
TransactionExecutionResult::NotExecuted(error) => BanksTransactionResultWithMetadata {
result: Err(error),
metadata: None,
},
TransactionExecutionResult::Executed { details, .. } => {
BanksTransactionResultWithMetadata {
result: details.status,
metadata: Some(TransactionMetadata {
compute_units_consumed: details.executed_units,
log_messages: details.log_messages.unwrap_or_default(),
return_data: details.return_data,
}),
}
}
}
}
async fn get_account_with_commitment_and_context(
self,
_: Context,

View File

@ -17,6 +17,7 @@ crossbeam-channel = "0.5"
log = "0.4.17"
serde = "1.0.144"
solana-banks-client = { path = "../banks-client", version = "=1.15.0" }
solana-banks-interface = { path = "../banks-interface", version = "=1.15.0" }
solana-banks-server = { path = "../banks-server", version = "=1.15.0" }
solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "=1.15.0" }
solana-logger = { path = "../logger", version = "=1.15.0" }

View File

@ -63,6 +63,7 @@ use {
// Export types so test clients can limit their solana crate dependencies
pub use {
solana_banks_client::{BanksClient, BanksClientError},
solana_banks_interface::BanksTransactionResultWithMetadata,
solana_program_runtime::invoke_context::InvokeContext,
solana_sdk::transaction_context::IndexOfAccount,
};

View File

@ -4878,6 +4878,7 @@ dependencies = [
"log",
"serde",
"solana-banks-client",
"solana-banks-interface",
"solana-banks-server",
"solana-bpf-loader-program",
"solana-logger 1.15.0",

View File

@ -2951,13 +2951,13 @@ pub(crate) mod tests {
&system_program::id(),
);
bank_forks
assert!(bank_forks
.read()
.unwrap()
.get(0)
.unwrap()
.process_transaction_with_logs(&tx)
.unwrap();
.process_transaction_with_metadata(tx.clone())
.was_executed());
subscriptions.notify_subscribers(CommitmentSlots::new_from_slot(0));

View File

@ -6170,24 +6170,37 @@ impl Bank {
.map_or(Ok(()), |sig| self.get_signature_status(sig).unwrap())
}
/// Process a Transaction and store program log data. This is used for unit tests, and simply
/// replicates the vector Bank::process_transactions method with `enable_cpi_recording: true`
pub fn process_transaction_with_logs(&self, tx: &Transaction) -> Result<()> {
let txs = vec![VersionedTransaction::from(tx.clone())];
let batch = self.prepare_entry_batch(txs)?;
let _results = self.load_execute_and_commit_transactions(
/// Process a Transaction and store metadata. This is used for tests and the banks services. It
/// replicates the vector Bank::process_transaction method with metadata recording enabled.
#[must_use]
pub fn process_transaction_with_metadata(
&self,
tx: impl Into<VersionedTransaction>,
) -> TransactionExecutionResult {
let txs = vec![tx.into()];
let batch = match self.prepare_entry_batch(txs) {
Ok(batch) => batch,
Err(err) => return TransactionExecutionResult::NotExecuted(err),
};
let (
TransactionResults {
mut execution_results,
..
},
..,
) = self.load_execute_and_commit_transactions(
&batch,
MAX_PROCESSING_AGE,
false,
false,
true,
false,
false, // collect_balances
false, // enable_cpi_recording
true, // enable_log_recording
true, // enable_return_data_recording
&mut ExecuteTimings::default(),
None,
Some(1000 * 1000),
);
tx.signatures
.get(0)
.map_or(Ok(()), |sig| self.get_signature_status(sig).unwrap())
execution_results.remove(0)
}
/// Process multiple transaction in a single batch. This is used for benches and unit tests.