transaction-status: Add return data to meta (#23688)

* transaction-status: Add return data to meta

* Add return data to simulation results

* Use pretty-hex for printing return data

* Update arg name, make TransactionRecord struct

* Rename TransactionRecord -> ExecutionRecord
This commit is contained in:
Jon Cinque 2022-03-22 23:17:05 +01:00 committed by GitHub
parent 359e2de090
commit 7af48465fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 439 additions and 45 deletions

2
Cargo.lock generated
View File

@ -4424,6 +4424,7 @@ dependencies = [
"ed25519-dalek",
"humantime",
"indicatif",
"pretty-hex",
"serde",
"serde_json",
"solana-account-decoder",
@ -5309,6 +5310,7 @@ dependencies = [
name = "solana-program-test"
version = "1.11.0"
dependencies = [
"assert_matches",
"async-trait",
"base64 0.13.0",
"bincode",

View File

@ -1,5 +1,8 @@
use {
solana_sdk::{transaction::TransactionError, transport::TransportError},
solana_sdk::{
transaction::TransactionError, transaction_context::TransactionReturnData,
transport::TransportError,
},
std::io,
tarpc::client::RpcError,
thiserror::Error,
@ -25,6 +28,7 @@ pub enum BanksClientError {
err: TransactionError,
logs: Vec<String>,
units_consumed: u64,
return_data: Option<TransactionReturnData>,
},
}

View File

@ -247,6 +247,7 @@ impl BanksClient {
err,
logs: simulation_details.logs,
units_consumed: simulation_details.units_consumed,
return_data: simulation_details.return_data,
}),
BanksTransactionResultWithSimulation {
result: Some(result),

View File

@ -12,6 +12,7 @@ use {
pubkey::Pubkey,
signature::Signature,
transaction::{self, Transaction, TransactionError},
transaction_context::TransactionReturnData,
},
};
@ -35,6 +36,7 @@ pub struct TransactionStatus {
pub struct TransactionSimulationDetails {
pub logs: Vec<String>,
pub units_consumed: u64,
pub return_data: Option<TransactionReturnData>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]

View File

@ -266,6 +266,7 @@ impl Banks for BanksServer {
logs,
post_simulation_accounts: _,
units_consumed,
return_data,
} = self
.bank(commitment)
.simulate_transaction_unchecked(sanitized_transaction)
@ -275,6 +276,7 @@ impl Banks for BanksServer {
simulation_details: Some(TransactionSimulationDetails {
logs,
units_consumed,
return_data,
}),
};
}

View File

@ -17,6 +17,7 @@ clap = "2.33.0"
console = "0.15.0"
humantime = "2.0.1"
indicatif = "0.16.2"
pretty-hex = "0.2.1"
serde = "1.0.136"
serde_json = "1.0.79"
solana-account-decoder = { path = "../account-decoder", version = "=1.11.0" }

View File

@ -14,6 +14,7 @@ use {
signature::Signature,
stake,
transaction::{TransactionError, TransactionVersion, VersionedTransaction},
transaction_context::TransactionReturnData,
},
solana_transaction_status::{Rewards, UiTransactionStatusMeta},
spl_memo::{id as spl_memo_id, v1::id as spl_memo_v1_id},
@ -246,6 +247,7 @@ fn write_transaction<W: io::Write>(
write_fees(w, transaction_status.fee, prefix)?;
write_balances(w, transaction_status, prefix)?;
write_log_messages(w, transaction_status.log_messages.as_ref(), prefix)?;
write_return_data(w, transaction_status.return_data.as_ref(), prefix)?;
write_rewards(w, transaction_status.rewards.as_ref(), prefix)?;
} else {
writeln!(w, "{}Status: Unavailable", prefix)?;
@ -576,6 +578,25 @@ fn write_balances<W: io::Write>(
Ok(())
}
fn write_return_data<W: io::Write>(
w: &mut W,
return_data: Option<&TransactionReturnData>,
prefix: &str,
) -> io::Result<()> {
if let Some(return_data) = return_data {
if !return_data.data.is_empty() {
use pretty_hex::*;
writeln!(
w,
"{}Return Data from Program {}:",
prefix, return_data.program_id
)?;
writeln!(w, "{} {:?}", prefix, return_data.data.hex_dump())?;
}
}
Ok(())
}
fn write_log_messages<W: io::Write>(
w: &mut W,
log_messages: Option<&Vec<String>>,
@ -750,6 +771,10 @@ mod test {
commission: None,
}]),
loaded_addresses: LoadedAddresses::default(),
return_data: Some(TransactionReturnData {
program_id: Pubkey::new_from_array([2u8; 32]),
data: vec![1, 2, 3],
}),
};
let output = {
@ -786,6 +811,9 @@ Status: Ok
Account 1 balance: 0.00001 -> 0.0000099
Log Messages:
Test message
Return Data from Program 8qbHbw2BbbTHBW1sbeqakYXVKRQM8Ne7pLK7m6CVfeR:
Length: 3 (0x3) bytes
0000: 01 02 03 ...
Rewards:
Address Type Amount New Balance \0
4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi rent -0.000000100 0.000009900 \0
@ -820,6 +848,10 @@ Rewards:
commission: None,
}]),
loaded_addresses,
return_data: Some(TransactionReturnData {
program_id: Pubkey::new_from_array([2u8; 32]),
data: vec![1, 2, 3],
}),
};
let output = {
@ -865,6 +897,9 @@ Status: Ok
Account 3 balance: 0.00002
Log Messages:
Test message
Return Data from Program 8qbHbw2BbbTHBW1sbeqakYXVKRQM8Ne7pLK7m6CVfeR:
Length: 3 (0x3) bytes
0000: 01 02 03 ...
Rewards:
Address Type Amount New Balance \0
CktRuQ2mttgRGkXJtyksdKHjUdc2C4TgDzyB98oEzy8 rent -0.000000100 0.000014900 \0

View File

@ -229,6 +229,7 @@ impl RpcSender for MockSender {
post_token_balances: None,
rewards: None,
loaded_addresses: None,
return_data: None,
}),
},
block_time: Some(1628633791),
@ -340,6 +341,7 @@ impl RpcSender for MockSender {
logs: None,
accounts: None,
units_consumed: None,
return_data: None,
},
})?,
"getMinimumBalanceForRentExemption" => json![20],

View File

@ -7,6 +7,7 @@ use {
hash::Hash,
inflation::Inflation,
transaction::{Result, TransactionError},
transaction_context::TransactionReturnData,
},
solana_transaction_status::{
ConfirmedTransactionStatusWithSignature, TransactionConfirmationStatus, UiConfirmedBlock,
@ -347,6 +348,7 @@ pub struct RpcSimulateTransactionResult {
pub logs: Option<Vec<String>>,
pub accounts: Option<Vec<Option<UiAccount>>>,
pub units_consumed: Option<u64>,
pub return_data: Option<TransactionReturnData>,
}
#[derive(Serialize, Deserialize, Clone, Debug)]

View File

@ -1180,6 +1180,7 @@ impl BankingStage {
MAX_PROCESSING_AGE,
transaction_status_sender.is_some(),
transaction_status_sender.is_some(),
transaction_status_sender.is_some(),
&mut execute_and_commit_timings.execute_timings,
)
},
@ -2149,6 +2150,7 @@ mod tests {
log_messages: None,
inner_instructions: None,
durable_nonce_fee: None,
return_data: None,
})
}

View File

@ -1331,7 +1331,7 @@ fn load_blockstore(
blockstore.clone(),
exit,
enable_rpc_transaction_history,
config.rpc_config.enable_cpi_and_log_storage,
config.rpc_config.enable_extended_tx_metadata_storage,
transaction_notifier,
)
} else {
@ -1538,7 +1538,7 @@ fn initialize_rpc_transaction_history_services(
blockstore: Arc<Blockstore>,
exit: &Arc<AtomicBool>,
enable_rpc_transaction_history: bool,
enable_cpi_and_log_storage: bool,
enable_extended_tx_metadata_storage: bool,
transaction_notifier: Option<TransactionNotifierLock>,
) -> TransactionHistoryServices {
let max_complete_transaction_status_slot = Arc::new(AtomicU64::new(blockstore.max_root()));
@ -1552,7 +1552,7 @@ fn initialize_rpc_transaction_history_services(
enable_rpc_transaction_history,
transaction_notifier.clone(),
blockstore.clone(),
enable_cpi_and_log_storage,
enable_extended_tx_metadata_storage,
exit,
));

View File

@ -4697,6 +4697,7 @@ pub mod tests {
pubkey::Pubkey,
signature::Signature,
transaction::{Transaction, TransactionError},
transaction_context::TransactionReturnData,
},
solana_storage_proto::convert::generated,
solana_transaction_status::{InnerInstructions, Reward, Rewards, TransactionTokenBalance},
@ -6858,6 +6859,7 @@ pub mod tests {
post_token_balances: Some(vec![]),
rewards: Some(vec![]),
loaded_addresses: LoadedAddresses::default(),
return_data: Some(TransactionReturnData::default()),
}
.into();
blockstore
@ -6875,6 +6877,7 @@ pub mod tests {
post_token_balances: Some(vec![]),
rewards: Some(vec![]),
loaded_addresses: LoadedAddresses::default(),
return_data: Some(TransactionReturnData::default()),
}
.into();
blockstore
@ -6892,6 +6895,7 @@ pub mod tests {
post_token_balances: Some(vec![]),
rewards: Some(vec![]),
loaded_addresses: LoadedAddresses::default(),
return_data: Some(TransactionReturnData::default()),
}
.into();
blockstore
@ -6911,6 +6915,7 @@ pub mod tests {
post_token_balances: Some(vec![]),
rewards: Some(vec![]),
loaded_addresses: LoadedAddresses::default(),
return_data: Some(TransactionReturnData::default()),
},
}
})
@ -7023,6 +7028,10 @@ pub mod tests {
writable: vec![Pubkey::new_unique()],
readonly: vec![Pubkey::new_unique()],
};
let test_return_data = TransactionReturnData {
program_id: Pubkey::new_unique(),
data: vec![1, 2, 3],
};
// result not found
assert!(transaction_status_cf
@ -7042,6 +7051,7 @@ pub mod tests {
post_token_balances: Some(post_token_balances_vec.clone()),
rewards: Some(rewards_vec.clone()),
loaded_addresses: test_loaded_addresses.clone(),
return_data: Some(test_return_data.clone()),
}
.into();
assert!(transaction_status_cf
@ -7060,6 +7070,7 @@ pub mod tests {
post_token_balances,
rewards,
loaded_addresses,
return_data,
} = transaction_status_cf
.get_protobuf_or_bincode::<StoredTransactionStatusMeta>((0, Signature::default(), 0))
.unwrap()
@ -7076,6 +7087,7 @@ pub mod tests {
assert_eq!(post_token_balances.unwrap(), post_token_balances_vec);
assert_eq!(rewards.unwrap(), rewards_vec);
assert_eq!(loaded_addresses, test_loaded_addresses);
assert_eq!(return_data.unwrap(), test_return_data);
// insert value
let status = TransactionStatusMeta {
@ -7089,6 +7101,7 @@ pub mod tests {
post_token_balances: Some(post_token_balances_vec.clone()),
rewards: Some(rewards_vec.clone()),
loaded_addresses: test_loaded_addresses.clone(),
return_data: Some(test_return_data.clone()),
}
.into();
assert!(transaction_status_cf
@ -7107,6 +7120,7 @@ pub mod tests {
post_token_balances,
rewards,
loaded_addresses,
return_data,
} = transaction_status_cf
.get_protobuf_or_bincode::<StoredTransactionStatusMeta>((
0,
@ -7129,6 +7143,7 @@ pub mod tests {
assert_eq!(post_token_balances.unwrap(), post_token_balances_vec);
assert_eq!(rewards.unwrap(), rewards_vec);
assert_eq!(loaded_addresses, test_loaded_addresses);
assert_eq!(return_data.unwrap(), test_return_data);
}
#[test]
@ -7357,6 +7372,7 @@ pub mod tests {
post_token_balances: Some(vec![]),
rewards: Some(vec![]),
loaded_addresses: LoadedAddresses::default(),
return_data: Some(TransactionReturnData::default()),
}
.into();
@ -7552,6 +7568,7 @@ pub mod tests {
post_token_balances: Some(vec![]),
rewards: Some(vec![]),
loaded_addresses: LoadedAddresses::default(),
return_data: Some(TransactionReturnData::default()),
}
.into();
@ -7723,6 +7740,10 @@ pub mod tests {
let post_token_balances = Some(vec![]);
let rewards = Some(vec![]);
let signature = transaction.signatures[0];
let return_data = Some(TransactionReturnData {
program_id: Pubkey::new_unique(),
data: vec![1, 2, 3],
});
let status = TransactionStatusMeta {
status: Ok(()),
fee: 42,
@ -7734,6 +7755,7 @@ pub mod tests {
post_token_balances: post_token_balances.clone(),
rewards: rewards.clone(),
loaded_addresses: LoadedAddresses::default(),
return_data: return_data.clone(),
}
.into();
blockstore
@ -7753,6 +7775,7 @@ pub mod tests {
post_token_balances,
rewards,
loaded_addresses: LoadedAddresses::default(),
return_data,
},
}
})
@ -7824,6 +7847,10 @@ pub mod tests {
let pre_token_balances = Some(vec![]);
let post_token_balances = Some(vec![]);
let rewards = Some(vec![]);
let return_data = Some(TransactionReturnData {
program_id: Pubkey::new_unique(),
data: vec![1, 2, 3],
});
let signature = transaction.signatures[0];
let status = TransactionStatusMeta {
status: Ok(()),
@ -7836,6 +7863,7 @@ pub mod tests {
post_token_balances: post_token_balances.clone(),
rewards: rewards.clone(),
loaded_addresses: LoadedAddresses::default(),
return_data: return_data.clone(),
}
.into();
blockstore
@ -7855,6 +7883,7 @@ pub mod tests {
post_token_balances,
rewards,
loaded_addresses: LoadedAddresses::default(),
return_data,
},
}
})
@ -8614,6 +8643,7 @@ pub mod tests {
post_token_balances: Some(vec![]),
rewards: Some(vec![]),
loaded_addresses: LoadedAddresses::default(),
return_data: Some(TransactionReturnData::default()),
}
.into();
transaction_status_cf
@ -9171,6 +9201,10 @@ pub mod tests {
commission: None,
}]),
loaded_addresses: LoadedAddresses::default(),
return_data: Some(TransactionReturnData {
program_id: Pubkey::new_unique(),
data: vec![1, 2, 3],
}),
};
let deprecated_status: StoredTransactionStatusMeta = status.clone().try_into().unwrap();
let protobuf_status: generated::TransactionStatusMeta = status.into();

View File

@ -181,6 +181,7 @@ fn execute_batch(
transaction_status_sender.is_some(),
transaction_status_sender.is_some(),
transaction_status_sender.is_some(),
transaction_status_sender.is_some(),
timings,
);
@ -3510,6 +3511,7 @@ pub mod tests {
false,
false,
false,
false,
&mut ExecuteTimings::default(),
);
let (err, signature) = get_first_error(&batch, fee_collection_results).unwrap();

View File

@ -55,6 +55,9 @@ while [[ -n $1 ]]; do
elif [[ $1 = --enable-cpi-and-log-storage ]]; then
args+=("$1")
shift
elif [[ $1 = --enable-extended-tx-metadata-storage ]]; then
args+=("$1")
shift
elif [[ $1 = --enable-rpc-bigtable-ledger-storage ]]; then
args+=("$1")
shift

View File

@ -141,6 +141,9 @@ while [[ -n $1 ]]; do
elif [[ $1 = --enable-cpi-and-log-storage ]]; then
args+=("$1")
shift
elif [[ $1 = --enable-extended-tx-metadata-storage ]]; then
args+=("$1")
shift
elif [[ $1 = --skip-poh-verify ]]; then
args+=("$1")
shift

View File

@ -280,7 +280,7 @@ EOF
if $maybeFullRpc; then
args+=(--enable-rpc-transaction-history)
args+=(--enable-cpi-and-log-storage)
args+=(--enable-extended-tx-metadata-storage)
fi
if [[ $airdropsEnabled = true ]]; then
@ -408,7 +408,7 @@ EOF
if $maybeFullRpc; then
args+=(--enable-rpc-transaction-history)
args+=(--enable-cpi-and-log-storage)
args+=(--enable-extended-tx-metadata-storage)
fi
cat >> ~/solana/on-reboot <<EOF

View File

@ -8,6 +8,7 @@ repository = "https://github.com/solana-labs/solana"
version = "1.11.0"
[dependencies]
assert_matches = "1.5.0"
async-trait = "0.1.52"
base64 = "0.13.0"
bincode = "1.3.3"

View File

@ -1,14 +1,19 @@
use {
assert_matches::assert_matches,
solana_banks_client::BanksClientError,
solana_program_test::{processor, ProgramTest},
solana_sdk::{
account_info::{next_account_info, AccountInfo},
commitment_config::CommitmentLevel,
entrypoint::ProgramResult,
instruction::{AccountMeta, Instruction},
msg,
program::{get_return_data, invoke, set_return_data},
program_error::ProgramError,
pubkey::Pubkey,
signature::Signer,
transaction::Transaction,
transaction_context::TransactionReturnData,
},
std::str::from_utf8,
};
@ -85,3 +90,55 @@ async fn return_data() {
.await
.unwrap();
}
// Process instruction to echo input back to another program
#[allow(clippy::unnecessary_wraps)]
fn error_set_return_data_process_instruction(
_program_id: &Pubkey,
_accounts: &[AccountInfo],
input: &[u8],
) -> ProgramResult {
set_return_data(input);
Err(ProgramError::InvalidInstructionData)
}
#[tokio::test]
async fn simulation_return_data() {
let error_set_return_data_program_id = Pubkey::new_unique();
let program_test = ProgramTest::new(
"error_set_return_data",
error_set_return_data_program_id,
processor!(error_set_return_data_process_instruction),
);
let mut context = program_test.start_with_context().await;
let expected_data = vec![240, 159, 166, 150];
let instructions = vec![Instruction {
program_id: error_set_return_data_program_id,
accounts: vec![],
data: expected_data.clone(),
}];
let transaction = Transaction::new_signed_with_payer(
&instructions,
Some(&context.payer.pubkey()),
&[&context.payer],
context.last_blockhash,
);
let error = context
.banks_client
.process_transaction_with_preflight_and_commitment(transaction, CommitmentLevel::Confirmed)
.await
.unwrap_err();
assert_matches!(
error,
BanksClientError::SimulationError {
return_data: Some(TransactionReturnData {
program_id,
data,
}),
..
} if program_id == error_set_return_data_program_id && data == expected_data
);
}

View File

@ -1862,6 +1862,12 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
[[package]]
name = "pretty-hex"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc5c99d529f0d30937f6f4b8a86d988047327bb88d04d2c4afc356de74722131"
[[package]]
name = "proc-macro-crate"
version = "0.1.5"
@ -3075,6 +3081,7 @@ dependencies = [
"console",
"humantime",
"indicatif",
"pretty-hex",
"serde",
"serde_json",
"solana-account-decoder",
@ -3419,6 +3426,7 @@ dependencies = [
name = "solana-program-test"
version = "1.11.0"
dependencies = [
"assert_matches",
"async-trait",
"base64 0.13.0",
"bincode",

View File

@ -323,6 +323,7 @@ fn process_transaction_and_record_inner(
false,
true,
false,
false,
&mut ExecuteTimings::default(),
)
.0;
@ -364,6 +365,7 @@ fn execute_transactions(
true,
true,
true,
true,
&mut timings,
);
let tx_post_token_balances = collect_token_balances(&bank, &batch, &mut mint_decimals);
@ -392,6 +394,7 @@ fn execute_transactions(
log_messages,
inner_instructions,
durable_nonce_fee,
return_data,
} = details;
let lamports_per_signature = match durable_nonce_fee {
@ -432,6 +435,7 @@ fn execute_transactions(
log_messages,
rewards: None,
loaded_addresses: LoadedAddresses::default(),
return_data,
};
Ok(ConfirmedTransactionWithStatusMeta {

View File

@ -143,7 +143,7 @@ fn is_finalized(
#[derive(Debug, Default, Clone)]
pub struct JsonRpcConfig {
pub enable_rpc_transaction_history: bool,
pub enable_cpi_and_log_storage: bool,
pub enable_extended_tx_metadata_storage: bool,
pub faucet_addr: Option<SocketAddr>,
pub health_check_slot_distance: u64,
pub rpc_bigtable_config: Option<RpcBigtableConfig>,
@ -3551,6 +3551,7 @@ pub mod rpc_full {
logs,
post_simulation_accounts: _,
units_consumed,
return_data,
} = preflight_bank.simulate_transaction(transaction)
{
match err {
@ -3568,6 +3569,7 @@ pub mod rpc_full {
logs: Some(logs),
accounts: None,
units_consumed: Some(units_consumed),
return_data,
},
}
.into());
@ -3625,6 +3627,7 @@ pub mod rpc_full {
logs,
post_simulation_accounts,
units_consumed,
return_data,
} = bank.simulate_transaction(transaction);
let accounts = if let Some(config_accounts) = config.accounts {
@ -3676,6 +3679,7 @@ pub mod rpc_full {
logs: Some(logs),
accounts,
units_consumed: Some(units_consumed),
return_data,
},
))
}
@ -5574,6 +5578,7 @@ pub mod tests {
"Program 11111111111111111111111111111111 invoke [1]",
"Program 11111111111111111111111111111111 success"
],
"returnData":null,
"unitsConsumed":0
}
},
@ -5660,6 +5665,7 @@ pub mod tests {
"Program 11111111111111111111111111111111 invoke [1]",
"Program 11111111111111111111111111111111 success"
],
"returnData":null,
"unitsConsumed":0
}
},
@ -5688,6 +5694,7 @@ pub mod tests {
"Program 11111111111111111111111111111111 invoke [1]",
"Program 11111111111111111111111111111111 success"
],
"returnData":null,
"unitsConsumed":0
}
},
@ -5737,6 +5744,7 @@ pub mod tests {
"err":"BlockhashNotFound",
"accounts":null,
"logs":[],
"returnData":null,
"unitsConsumed":0
}
},
@ -5766,6 +5774,7 @@ pub mod tests {
"Program 11111111111111111111111111111111 invoke [1]",
"Program 11111111111111111111111111111111 success"
],
"returnData":null,
"unitsConsumed":0
}
},
@ -6123,7 +6132,7 @@ pub mod tests {
assert_eq!(
res,
Some(
r#"{"jsonrpc":"2.0","error":{"code":-32002,"message":"Transaction simulation failed: Blockhash not found","data":{"accounts":null,"err":"BlockhashNotFound","logs":[],"unitsConsumed":0}},"id":1}"#.to_string(),
r#"{"jsonrpc":"2.0","error":{"code":-32002,"message":"Transaction simulation failed: Blockhash not found","data":{"accounts":null,"err":"BlockhashNotFound","logs":[],"returnData":null,"unitsConsumed":0}},"id":1}"#.to_string(),
)
);

View File

@ -34,7 +34,7 @@ impl TransactionStatusService {
enable_rpc_transaction_history: bool,
transaction_notifier: Option<TransactionNotifierLock>,
blockstore: Arc<Blockstore>,
enable_cpi_and_log_storage: bool,
enable_extended_tx_metadata_storage: bool,
exit: &Arc<AtomicBool>,
) -> Self {
let exit = exit.clone();
@ -51,7 +51,7 @@ impl TransactionStatusService {
enable_rpc_transaction_history,
transaction_notifier.clone(),
&blockstore,
enable_cpi_and_log_storage,
enable_extended_tx_metadata_storage,
) {
break;
}
@ -66,7 +66,7 @@ impl TransactionStatusService {
enable_rpc_transaction_history: bool,
transaction_notifier: Option<TransactionNotifierLock>,
blockstore: &Arc<Blockstore>,
enable_cpi_and_log_storage: bool,
enable_extended_tx_metadata_storage: bool,
) -> Result<(), RecvTimeoutError> {
match write_transaction_status_receiver.recv_timeout(Duration::from_secs(1))? {
TransactionStatusMessage::Batch(TransactionStatusBatch {
@ -101,6 +101,7 @@ impl TransactionStatusService {
log_messages,
inner_instructions,
durable_nonce_fee,
return_data,
} = details;
let lamports_per_signature = match durable_nonce_fee {
Some(DurableNonceFee::Valid(lamports_per_signature)) => {
@ -156,6 +157,7 @@ impl TransactionStatusService {
post_token_balances,
rewards,
loaded_addresses,
return_data,
};
if let Some(transaction_notifier) = transaction_notifier.as_ref() {
@ -167,9 +169,11 @@ impl TransactionStatusService {
);
}
if !(enable_cpi_and_log_storage || transaction_notifier.is_some()) {
if !(enable_extended_tx_metadata_storage || transaction_notifier.is_some())
{
transaction_status_meta.log_messages.take();
transaction_status_meta.inner_instructions.take();
transaction_status_meta.return_data.take();
}
if enable_rpc_transaction_history {
@ -347,6 +351,7 @@ pub(crate) mod tests {
)
.unwrap(),
)),
return_data: None,
});
let balances = TransactionBalancesSet {

View File

@ -1374,6 +1374,7 @@ mod tests {
log_messages: None,
inner_instructions: None,
durable_nonce_fee: nonce.map(DurableNonceFee::from),
return_data: None,
})
}

View File

@ -130,7 +130,10 @@ use {
MessageHash, Result, SanitizedTransaction, Transaction, TransactionError,
TransactionVerificationMode, VersionedTransaction,
},
transaction_context::{InstructionTrace, TransactionAccount, TransactionContext},
transaction_context::{
ExecutionRecord, InstructionTrace, TransactionAccount, TransactionContext,
TransactionReturnData,
},
},
solana_stake_program::stake_state::{
self, InflationPointCalculationEvent, PointValue, StakeState,
@ -579,6 +582,7 @@ pub struct TransactionExecutionDetails {
pub log_messages: Option<Vec<String>>,
pub inner_instructions: Option<InnerInstructionsList>,
pub durable_nonce_fee: Option<DurableNonceFee>,
pub return_data: Option<TransactionReturnData>,
}
/// Type safe representation of a transaction execution attempt which
@ -670,6 +674,7 @@ pub struct TransactionSimulationResult {
pub logs: TransactionLogMessages,
pub post_simulation_accounts: Vec<TransactionAccount>,
pub units_consumed: u64,
pub return_data: Option<TransactionReturnData>,
}
pub struct TransactionBalancesSet {
pub pre_balances: TransactionBalances,
@ -3541,6 +3546,7 @@ impl Bank {
MAX_PROCESSING_AGE - MAX_TRANSACTION_FORWARDING_DELAY,
false,
true,
true,
&mut timings,
);
@ -3571,17 +3577,20 @@ impl Bank {
let execution_result = execution_results.pop().unwrap();
let flattened_result = execution_result.flattened_result();
let logs = match execution_result {
TransactionExecutionResult::Executed(details) => details.log_messages,
TransactionExecutionResult::NotExecuted(_) => None,
}
.unwrap_or_default();
let (logs, return_data) = match execution_result {
TransactionExecutionResult::Executed(details) => {
(details.log_messages, details.return_data)
}
TransactionExecutionResult::NotExecuted(_) => (None, None),
};
let logs = logs.unwrap_or_default();
TransactionSimulationResult {
result: flattened_result,
logs,
post_simulation_accounts,
units_consumed,
return_data,
}
}
@ -3858,6 +3867,7 @@ impl Bank {
/// Execute a transaction using the provided loaded accounts and update
/// the executors cache if the transaction was successful.
#[allow(clippy::too_many_arguments)]
fn execute_loaded_transaction(
&self,
tx: &SanitizedTransaction,
@ -3866,6 +3876,7 @@ impl Bank {
durable_nonce_fee: Option<DurableNonceFee>,
enable_cpi_recording: bool,
enable_log_recording: bool,
enable_return_data_recording: bool,
timings: &mut ExecuteTimings,
error_counters: &mut ErrorCounters,
) -> TransactionExecutionResult {
@ -3960,7 +3971,11 @@ impl Bank {
.ok()
});
let (accounts, instruction_trace) = transaction_context.deconstruct();
let ExecutionRecord {
accounts,
instruction_trace,
mut return_data,
} = transaction_context.into();
loaded_transaction.accounts = accounts;
let inner_instructions = if enable_cpi_recording {
@ -3971,11 +3986,25 @@ impl Bank {
None
};
let return_data = if enable_return_data_recording {
if let Some(end_index) = return_data.data.iter().rposition(|&x| x != 0) {
let end_index = end_index.saturating_add(1);
error!("end index {}", end_index);
return_data.data.truncate(end_index);
Some(return_data)
} else {
None
}
} else {
None
};
TransactionExecutionResult::Executed(TransactionExecutionDetails {
status,
log_messages,
inner_instructions,
durable_nonce_fee,
return_data,
})
}
@ -3986,6 +4015,7 @@ impl Bank {
max_age: usize,
enable_cpi_recording: bool,
enable_log_recording: bool,
enable_return_data_recording: bool,
timings: &mut ExecuteTimings,
) -> LoadAndExecuteTransactionsOutput {
let sanitized_txs = batch.sanitized_transactions();
@ -4089,6 +4119,7 @@ impl Bank {
durable_nonce_fee,
enable_cpi_recording,
enable_log_recording,
enable_return_data_recording,
timings,
&mut error_counters,
)
@ -5240,6 +5271,7 @@ impl Bank {
collect_balances: bool,
enable_cpi_recording: bool,
enable_log_recording: bool,
enable_return_data_recording: bool,
timings: &mut ExecuteTimings,
) -> (TransactionResults, TransactionBalancesSet) {
let pre_balances = if collect_balances {
@ -5260,6 +5292,7 @@ impl Bank {
max_age,
enable_cpi_recording,
enable_log_recording,
enable_return_data_recording,
timings,
);
@ -5346,6 +5379,7 @@ impl Bank {
false,
false,
false,
false,
&mut ExecuteTimings::default(),
)
.0
@ -6774,6 +6808,7 @@ pub(crate) mod tests {
message::{Message, MessageHeader},
nonce,
poh_config::PohConfig,
program::MAX_RETURN_DATA,
rent::Rent,
signature::{keypair_from_seed, Keypair, Signer},
stake::{
@ -6813,6 +6848,7 @@ pub(crate) mod tests {
log_messages: None,
inner_instructions: None,
durable_nonce_fee: nonce.map(DurableNonceFee::from),
return_data: None,
})
}
@ -9949,6 +9985,7 @@ pub(crate) mod tests {
false,
false,
false,
false,
&mut ExecuteTimings::default(),
)
.0
@ -12482,6 +12519,7 @@ pub(crate) mod tests {
true,
false,
false,
false,
&mut ExecuteTimings::default(),
);
@ -15407,6 +15445,7 @@ pub(crate) mod tests {
false,
false,
true,
false,
&mut ExecuteTimings::default(),
)
.0
@ -15449,6 +15488,91 @@ pub(crate) mod tests {
assert!(failure_log.contains(&"failed".to_string()));
}
#[test]
fn test_tx_return_data() {
solana_logger::setup();
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config_with_leader(
1_000_000_000_000_000,
&Pubkey::new_unique(),
bootstrap_validator_stake_lamports(),
);
let mut bank = Bank::new_for_tests(&genesis_config);
let mock_program_id = Pubkey::new(&[2u8; 32]);
fn mock_process_instruction(
_first_instruction_account: usize,
data: &[u8],
invoke_context: &mut InvokeContext,
) -> result::Result<(), InstructionError> {
let mock_program_id = Pubkey::new(&[2u8; 32]);
let transaction_context = &mut invoke_context.transaction_context;
let mut return_data = [0u8; MAX_RETURN_DATA];
if !data.is_empty() {
let index = usize::from_le_bytes(data.try_into().unwrap());
return_data[index] = 1;
transaction_context
.set_return_data(mock_program_id, return_data.to_vec())
.unwrap();
}
Ok(())
}
let blockhash = bank.last_blockhash();
bank.add_builtin("mock_program", &mock_program_id, mock_process_instruction);
for index in [
None,
Some(0),
Some(MAX_RETURN_DATA / 2),
Some(MAX_RETURN_DATA - 1),
] {
let data = if let Some(index) = index {
usize::to_le_bytes(index).to_vec()
} else {
Vec::new()
};
let txs = vec![Transaction::new_signed_with_payer(
&[Instruction {
program_id: mock_program_id,
data,
accounts: vec![AccountMeta::new(Pubkey::new_unique(), false)],
}],
Some(&mint_keypair.pubkey()),
&[&mint_keypair],
blockhash,
)];
let batch = bank.prepare_batch_for_tests(txs);
let return_data = bank
.load_execute_and_commit_transactions(
&batch,
MAX_PROCESSING_AGE,
false,
false,
false,
true,
&mut ExecuteTimings::default(),
)
.0
.execution_results[0]
.details()
.unwrap()
.return_data
.clone();
if let Some(index) = index {
let return_data = return_data.unwrap();
assert_eq!(return_data.program_id, mock_program_id);
let mut expected_data = vec![0u8; index];
expected_data.push(1u8);
assert_eq!(return_data.data, expected_data);
} else {
assert!(return_data.is_none());
}
}
}
#[test]
fn test_get_largest_accounts() {
let GenesisConfigInfo { genesis_config, .. } =

View File

@ -108,7 +108,7 @@ args=(
--rpc-faucet-address 127.0.0.1:9900
--log -
--enable-rpc-transaction-history
--enable-cpi-and-log-storage
--enable-extended-tx-metadata-storage
--init-complete-file "$dataDir"/init-completed
--snapshot-compression none
--require-tower

View File

@ -35,7 +35,7 @@ pub struct TransactionContext {
instruction_stack: Vec<usize>,
number_of_instructions_at_transaction_level: usize,
instruction_trace: InstructionTrace,
return_data: (Pubkey, Vec<u8>),
return_data: TransactionReturnData,
}
impl TransactionContext {
@ -57,25 +57,10 @@ impl TransactionContext {
instruction_stack: Vec::with_capacity(instruction_context_capacity),
number_of_instructions_at_transaction_level,
instruction_trace: Vec::with_capacity(number_of_instructions_at_transaction_level),
return_data: (Pubkey::default(), Vec::new()),
return_data: TransactionReturnData::default(),
}
}
/// Used by the bank in the runtime to write back the processed accounts and recorded instructions
pub fn deconstruct(self) -> (Vec<TransactionAccount>, Vec<Vec<InstructionContext>>) {
(
Vec::from(Pin::into_inner(self.account_keys))
.into_iter()
.zip(
Vec::from(Pin::into_inner(self.accounts))
.into_iter()
.map(|account| account.into_inner()),
)
.collect(),
self.instruction_trace,
)
}
/// Used in mock_process_instruction
pub fn deconstruct_without_keys(self) -> Result<Vec<AccountSharedData>, InstructionError> {
if !self.instruction_stack.is_empty() {
@ -225,7 +210,7 @@ impl TransactionContext {
/// Gets the return data of the current InstructionContext or any above
pub fn get_return_data(&self) -> (&Pubkey, &[u8]) {
(&self.return_data.0, &self.return_data.1)
(&self.return_data.program_id, &self.return_data.data)
}
/// Set the return data of the current InstructionContext
@ -234,7 +219,7 @@ impl TransactionContext {
program_id: Pubkey,
data: Vec<u8>,
) -> Result<(), InstructionError> {
self.return_data = (program_id, data);
self.return_data = TransactionReturnData { program_id, data };
Ok(())
}
@ -254,6 +239,13 @@ impl TransactionContext {
}
}
/// Return data at the end of a transaction
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
pub struct TransactionReturnData {
pub program_id: Pubkey,
pub data: Vec<u8>,
}
/// List of (stack height, instruction) for each top-level instruction
pub type InstructionTrace = Vec<Vec<InstructionContext>>;
@ -628,3 +620,27 @@ impl<'a> BorrowedAccount<'a> {
.unwrap_or_default()
}
}
/// Everything that needs to be recorded from a TransactionContext after execution
pub struct ExecutionRecord {
pub accounts: Vec<TransactionAccount>,
pub instruction_trace: InstructionTrace,
pub return_data: TransactionReturnData,
}
/// Used by the bank in the runtime to write back the processed accounts and recorded instructions
impl From<TransactionContext> for ExecutionRecord {
fn from(context: TransactionContext) -> Self {
Self {
accounts: Vec::from(Pin::into_inner(context.account_keys))
.into_iter()
.zip(
Vec::from(Pin::into_inner(context.accounts))
.into_iter()
.map(|account| account.into_inner()),
)
.collect(),
instruction_trace: context.instruction_trace,
return_data: context.return_data,
}
}
}

View File

@ -792,7 +792,7 @@ mod tests {
prost::Message,
solana_sdk::{
hash::Hash, message::v0::LoadedAddresses, signature::Keypair, system_transaction,
transaction::VersionedTransaction,
transaction::VersionedTransaction, transaction_context::TransactionReturnData,
},
solana_storage_proto::convert::generated,
solana_transaction_status::{
@ -842,6 +842,7 @@ mod tests {
post_token_balances: Some(vec![]),
rewards: Some(vec![]),
loaded_addresses: LoadedAddresses::default(),
return_data: Some(TransactionReturnData::default()),
},
});
let expected_block = ConfirmedBlock {
@ -899,6 +900,7 @@ mod tests {
meta.pre_token_balances = None; // Legacy bincode implementation does not support token balances
meta.post_token_balances = None; // Legacy bincode implementation does not support token balances
meta.rewards = None; // Legacy bincode implementation does not support rewards
meta.return_data = None; // Legacy bincode implementation does not support return data
}
assert_eq!(block, bincode_block.into());
} else {

View File

@ -237,6 +237,7 @@ impl From<StoredConfirmedBlockTransactionStatusMeta> for TransactionStatusMeta {
post_token_balances: None,
rewards: None,
loaded_addresses: LoadedAddresses::default(),
return_data: None,
}
}
}

View File

@ -57,6 +57,8 @@ message TransactionStatusMeta {
repeated Reward rewards = 9;
repeated bytes loaded_writable_addresses = 12;
repeated bytes loaded_readonly_addresses = 13;
ReturnData return_data = 14;
bool return_data_none = 15;
}
message TransactionError {
@ -88,6 +90,11 @@ message UiTokenAmount {
string ui_amount_string = 4;
}
message ReturnData {
bytes program_id = 1;
bytes data = 2;
}
enum RewardType {
Unspecified = 0;
Fee = 1;

View File

@ -12,6 +12,7 @@ use {
pubkey::Pubkey,
signature::Signature,
transaction::{Transaction, TransactionError, VersionedTransaction},
transaction_context::TransactionReturnData,
},
solana_transaction_status::{
ConfirmedBlock, InnerInstructions, Reward, RewardType, TransactionByAddrInfo,
@ -363,6 +364,7 @@ impl From<TransactionStatusMeta> for generated::TransactionStatusMeta {
post_token_balances,
rewards,
loaded_addresses,
return_data,
} = value;
let err = match status {
Ok(()) => None,
@ -403,6 +405,8 @@ impl From<TransactionStatusMeta> for generated::TransactionStatusMeta {
.into_iter()
.map(|key| <Pubkey as AsRef<[u8]>>::as_ref(&key).into())
.collect();
let return_data_none = return_data.is_none();
let return_data = return_data.map(|return_data| return_data.into());
Self {
err,
@ -418,6 +422,8 @@ impl From<TransactionStatusMeta> for generated::TransactionStatusMeta {
rewards,
loaded_writable_addresses,
loaded_readonly_addresses,
return_data,
return_data_none,
}
}
}
@ -447,6 +453,8 @@ impl TryFrom<generated::TransactionStatusMeta> for TransactionStatusMeta {
rewards,
loaded_writable_addresses,
loaded_readonly_addresses,
return_data,
return_data_none,
} = value;
let status = match &err {
None => Ok(()),
@ -490,6 +498,11 @@ impl TryFrom<generated::TransactionStatusMeta> for TransactionStatusMeta {
.map(|key| Pubkey::new(&key))
.collect(),
};
let return_data = if return_data_none {
None
} else {
return_data.map(|return_data| return_data.into())
};
Ok(Self {
status,
fee,
@ -501,6 +514,7 @@ impl TryFrom<generated::TransactionStatusMeta> for TransactionStatusMeta {
post_token_balances,
rewards,
loaded_addresses,
return_data,
})
}
}
@ -587,6 +601,24 @@ impl From<generated::MessageAddressTableLookup> for MessageAddressTableLookup {
}
}
impl From<TransactionReturnData> for generated::ReturnData {
fn from(value: TransactionReturnData) -> Self {
Self {
program_id: <Pubkey as AsRef<[u8]>>::as_ref(&value.program_id).into(),
data: value.data,
}
}
}
impl From<generated::ReturnData> for TransactionReturnData {
fn from(value: generated::ReturnData) -> Self {
Self {
program_id: Pubkey::new(&value.program_id),
data: value.data,
}
}
}
impl From<CompiledInstruction> for generated::CompiledInstruction {
fn from(value: CompiledInstruction) -> Self {
Self {

View File

@ -6,6 +6,7 @@ use {
},
solana_sdk::{
deserialize_utils::default_on_eof, message::v0::LoadedAddresses, transaction::Result,
transaction_context::TransactionReturnData,
},
solana_transaction_status::{
InnerInstructions, Reward, RewardType, TransactionStatusMeta, TransactionTokenBalance,
@ -167,6 +168,8 @@ pub struct StoredTransactionStatusMeta {
pub post_token_balances: Option<Vec<StoredTransactionTokenBalance>>,
#[serde(deserialize_with = "default_on_eof")]
pub rewards: Option<Vec<StoredExtendedReward>>,
#[serde(deserialize_with = "default_on_eof")]
pub return_data: Option<TransactionReturnData>,
}
impl From<StoredTransactionStatusMeta> for TransactionStatusMeta {
@ -181,6 +184,7 @@ impl From<StoredTransactionStatusMeta> for TransactionStatusMeta {
pre_token_balances,
post_token_balances,
rewards,
return_data,
} = value;
Self {
status,
@ -196,6 +200,7 @@ impl From<StoredTransactionStatusMeta> for TransactionStatusMeta {
rewards: rewards
.map(|rewards| rewards.into_iter().map(|reward| reward.into()).collect()),
loaded_addresses: LoadedAddresses::default(),
return_data,
}
}
}
@ -214,6 +219,7 @@ impl TryFrom<TransactionStatusMeta> for StoredTransactionStatusMeta {
post_token_balances,
rewards,
loaded_addresses,
return_data,
} = value;
if !loaded_addresses.is_empty() {
@ -237,6 +243,7 @@ impl TryFrom<TransactionStatusMeta> for StoredTransactionStatusMeta {
.map(|balances| balances.into_iter().map(|balance| balance.into()).collect()),
rewards: rewards
.map(|rewards| rewards.into_iter().map(|reward| reward.into()).collect()),
return_data,
})
}
}

View File

@ -22,6 +22,7 @@ use {
Result as TransactionResult, Transaction, TransactionError, TransactionVersion,
VersionedTransaction,
},
transaction_context::TransactionReturnData,
},
std::fmt,
thiserror::Error,
@ -279,6 +280,7 @@ pub struct TransactionStatusMeta {
pub post_token_balances: Option<Vec<TransactionTokenBalance>>,
pub rewards: Option<Rewards>,
pub loaded_addresses: LoadedAddresses,
pub return_data: Option<TransactionReturnData>,
}
impl Default for TransactionStatusMeta {
@ -294,6 +296,7 @@ impl Default for TransactionStatusMeta {
post_token_balances: None,
rewards: None,
loaded_addresses: LoadedAddresses::default(),
return_data: None,
}
}
}
@ -314,6 +317,7 @@ pub struct UiTransactionStatusMeta {
pub rewards: Option<Rewards>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub loaded_addresses: Option<UiLoadedAddresses>,
pub return_data: Option<TransactionReturnData>,
}
/// A duplicate representation of LoadedAddresses
@ -364,6 +368,7 @@ impl UiTransactionStatusMeta {
.map(|balance| balance.into_iter().map(Into::into).collect()),
rewards: meta.rewards,
loaded_addresses: Some(UiLoadedAddresses::from(&meta.loaded_addresses)),
return_data: meta.return_data,
}
}
}
@ -388,6 +393,7 @@ impl From<TransactionStatusMeta> for UiTransactionStatusMeta {
.map(|balance| balance.into_iter().map(Into::into).collect()),
rewards: meta.rewards,
loaded_addresses: Some(UiLoadedAddresses::from(&meta.loaded_addresses)),
return_data: meta.return_data,
}
}
}

View File

@ -657,7 +657,7 @@ fn main() {
)
.rpc_config(JsonRpcConfig {
enable_rpc_transaction_history: true,
enable_cpi_and_log_storage: true,
enable_extended_tx_metadata_storage: true,
rpc_bigtable_config,
faucet_addr,
..JsonRpcConfig::default_for_test()

View File

@ -656,8 +656,18 @@ pub fn main() {
.long("enable-cpi-and-log-storage")
.requires("enable_rpc_transaction_history")
.takes_value(false)
.help("Include CPI inner instructions and logs in the \
historical transaction info stored"),
.hidden(true)
.help("Deprecated, please use \"enable-extended-tx-metadata-storage\". \
Include CPI inner instructions, logs and return data in \
the historical transaction info stored"),
)
.arg(
Arg::with_name("enable_extended_tx_metadata_storage")
.long("enable-extended-tx-metadata-storage")
.requires("enable_rpc_transaction_history")
.takes_value(false)
.help("Include CPI inner instructions, logs, and return data in \
the historical transaction info stored"),
)
.arg(
Arg::with_name("rpc_max_multiple_accounts")
@ -2294,7 +2304,15 @@ pub fn main() {
};
if matches.is_present("minimal_rpc_api") {
warn!("--minimal-rpc-api is now the default behavior. This flag is deprecated and can be removed from the launch args")
warn!("--minimal-rpc-api is now the default behavior. This flag is deprecated and can be removed from the launch args");
}
if matches.is_present("enable_cpi_and_log_storage") {
warn!(
"--enable-cpi-and-log-storage is deprecated. Please update the \
launch args to use --enable-extended-tx-metadata-storage and remove \
--enable-cpi-and-log-storage"
);
}
let rpc_bigtable_config = if matches.is_present("enable_rpc_bigtable_ledger_storage")
@ -2325,7 +2343,8 @@ pub fn main() {
new_hard_forks: hardforks_of(&matches, "hard_forks"),
rpc_config: JsonRpcConfig {
enable_rpc_transaction_history: matches.is_present("enable_rpc_transaction_history"),
enable_cpi_and_log_storage: matches.is_present("enable_cpi_and_log_storage"),
enable_extended_tx_metadata_storage: matches.is_present("enable_cpi_and_log_storage")
|| matches.is_present("enable_extended_tx_metadata_storage"),
rpc_bigtable_config,
faucet_addr: matches.value_of("rpc_faucet_addr").map(|address| {
solana_net_utils::parse_host_port(address).expect("failed to parse faucet address")