Skip to content
This repository was archived by the owner on Jan 22, 2025. It is now read-only.

RPC simulateTransaction endpoint now returns program log output #10432

Merged
merged 1 commit into from
Jun 6, 2020
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
2 changes: 1 addition & 1 deletion client/src/rpc_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ impl RpcClient {
&self,
transaction: &Transaction,
sig_verify: bool,
) -> RpcResult<TransactionStatus> {
) -> RpcResult<RpcSimulateTransactionResult> {
let serialized_encoded = bs58::encode(serialize(transaction).unwrap()).into_string();
self.send(
RpcRequest::SimulateTransaction,
Expand Down
7 changes: 7 additions & 0 deletions client/src/rpc_response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,13 @@ pub struct RpcSignatureConfirmation {
pub status: Result<()>,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct RpcSimulateTransactionResult {
pub err: Option<TransactionError>,
pub logs: Option<Vec<String>>,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct RpcStorageTurn {
Expand Down
2 changes: 1 addition & 1 deletion core/src/banking_stage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,7 @@ impl BankingStage {
vec![]
};
let (mut loaded_accounts, results, mut retryable_txs, tx_count, signature_count) =
bank.load_and_execute_transactions(batch, MAX_PROCESSING_AGE);
bank.load_and_execute_transactions(batch, MAX_PROCESSING_AGE, None);
load_execute_time.stop();

let freeze_lock = bank.freeze_lock();
Expand Down
47 changes: 28 additions & 19 deletions core/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use solana_ledger::{
bank_forks::BankForks, blockstore::Blockstore, blockstore_db::BlockstoreError,
};
use solana_perf::packet::PACKET_DATA_SIZE;
use solana_runtime::{accounts::AccountAddressFilter, bank::Bank};
use solana_runtime::{accounts::AccountAddressFilter, bank::Bank, log_collector::LogCollector};
use solana_sdk::{
clock::{Epoch, Slot, UnixTimestamp},
commitment_config::{CommitmentConfig, CommitmentLevel},
Expand Down Expand Up @@ -718,14 +718,22 @@ fn verify_signature(input: &str) -> Result<Signature> {
}

/// Run transactions against a frozen bank without committing the results
fn run_transaction_simulation(bank: &Bank, transaction: Transaction) -> transaction::Result<()> {
fn run_transaction_simulation(
bank: &Bank,
transaction: Transaction,
) -> (transaction::Result<()>, Vec<String>) {
assert!(bank.is_frozen(), "simulation bank must be frozen");

let txs = &[transaction];
let batch = bank.prepare_simulation_batch(txs);
let log_collector = LogCollector::default();
let (_loaded_accounts, executed, _retryable_transactions, _transaction_count, _signature_count) =
bank.load_and_execute_transactions(&batch, solana_sdk::clock::MAX_PROCESSING_AGE);
executed[0].0.clone().map(|_| ())
bank.load_and_execute_transactions(
&batch,
solana_sdk::clock::MAX_PROCESSING_AGE,
Some(&log_collector),
);
(executed[0].0.clone().map(|_| ()), log_collector.output())
}

#[derive(Clone)]
Expand Down Expand Up @@ -932,7 +940,7 @@ pub trait RpcSol {
meta: Self::Metadata,
data: String,
config: Option<RpcSimulateTransactionConfig>,
) -> RpcResponse<TransactionStatus>;
) -> RpcResponse<RpcSimulateTransactionResult>;

#[rpc(meta, name = "getSlotLeader")]
fn get_slot_leader(
Expand Down Expand Up @@ -1452,7 +1460,7 @@ impl RpcSol for RpcSolImpl {
}

let bank = &*meta.request_processor.read().unwrap().bank(None)?;
if let Err(err) = run_transaction_simulation(&bank, transaction) {
if let (Err(err), _log_output) = run_transaction_simulation(&bank, transaction) {
// Note: it's possible that the transaction simulation failed but the actual
// transaction would succeed, such as when a transaction depends on an earlier
// transaction that has yet to reach max confirmations. In these cases the user
Expand Down Expand Up @@ -1486,7 +1494,7 @@ impl RpcSol for RpcSolImpl {
meta: Self::Metadata,
data: String,
config: Option<RpcSimulateTransactionConfig>,
) -> RpcResponse<TransactionStatus> {
) -> RpcResponse<RpcSimulateTransactionResult> {
let (_, transaction) = deserialize_bs58_transaction(data)?;
let config = config.unwrap_or_default();

Expand All @@ -1497,18 +1505,19 @@ impl RpcSol for RpcSolImpl {
};

let bank = &*meta.request_processor.read().unwrap().bank(None)?;

if result.is_ok() {
result = run_transaction_simulation(&bank, transaction);
}
let logs = if result.is_ok() {
let sim_result = run_transaction_simulation(&bank, transaction);
result = sim_result.0;
Some(sim_result.1)
} else {
None
};

new_response(
&bank,
TransactionStatus {
slot: bank.slot(),
confirmations: Some(0),
status: result.clone(),
RpcSimulateTransactionResult {
err: result.err(),
logs,
},
)
}
Expand Down Expand Up @@ -2400,7 +2409,7 @@ pub mod tests {
"jsonrpc": "2.0",
"result": {
"context":{"slot":0},
"value":{"confirmations":0,"slot": 0,"status":{"Ok":null},"err":null}
"value":{"err":null, "logs":[]}
},
"id": 1,
});
Expand All @@ -2420,7 +2429,7 @@ pub mod tests {
"jsonrpc": "2.0",
"result": {
"context":{"slot":0},
"value":{"confirmations":0,"slot":0,"status":{"Err":"SignatureFailure"},"err":"SignatureFailure"}
"value":{"err":"SignatureFailure", "logs":null}
},
"id": 1,
});
Expand All @@ -2440,7 +2449,7 @@ pub mod tests {
"jsonrpc": "2.0",
"result": {
"context":{"slot":0},
"value":{"confirmations":0,"slot": 0,"status":{"Ok":null},"err":null}
"value":{"err":null, "logs":[]}
},
"id": 1,
});
Expand All @@ -2460,7 +2469,7 @@ pub mod tests {
"jsonrpc": "2.0",
"result": {
"context":{"slot":0},
"value":{"confirmations":0,"slot": 0,"status":{"Ok":null},"err":null}
"value":{"err":null, "logs":[]}
},
"id": 1,
});
Expand Down
4 changes: 4 additions & 0 deletions docs/src/apps/jsonrpc-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1107,6 +1107,10 @@ Simulate sending a transaction
#### Results:

An RpcResponse containing a TransactionStatus object
The result will be an RpcResponse JSON object with `value` set to a JSON object with the following fields:

* `err: <object | null>` - Error if transaction failed, null if transaction succeeded. [TransactionError definitions](https://github.com/solana-labs/solana/blob/master/sdk/src/transaction.rs#L14)
* `logs: <array | null>` - Array of log messages the transaction instructions output during execution, null if simulation failed before the transaction was able to execute (for example due to an invalid blockhash or signature verification failure)

#### Example:

Expand Down
4 changes: 4 additions & 0 deletions programs/bpf/benches/bpf_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,4 +155,8 @@ impl InvokeContext for MockInvokeContext {
fn get_programs(&self) -> &[(Pubkey, ProcessInstruction)] {
&[]
}
fn log_enabled(&self) -> bool {
false
}
fn log(&mut self, _message: &str) {}
}
8 changes: 8 additions & 0 deletions programs/bpf_loader/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ mod tests {
#[derive(Debug, Default)]
pub struct MockInvokeContext {
key: Pubkey,
pub log: Vec<String>,
}
impl InvokeContext for MockInvokeContext {
fn push(&mut self, _key: &Pubkey) -> Result<(), InstructionError> {
Expand All @@ -287,6 +288,13 @@ mod tests {
fn get_programs(&self) -> &[(Pubkey, ProcessInstruction)] {
&[]
}
fn log_enabled(&self) -> bool {
true
}
fn log(&mut self, message: &str) {
info!("[MockInvokeContext::log] {}", message);
self.log.push(message.to_string());
}
}

#[test]
Expand Down
Loading