Browse Source

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
Jon Cinque 3 năm trước cách đây
mục cha
commit
7af48465fa

+ 2 - 0
Cargo.lock

@@ -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",

+ 5 - 1
banks-client/src/error.rs

@@ -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>,
     },
 }
 

+ 1 - 0
banks-client/src/lib.rs

@@ -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),

+ 2 - 0
banks-interface/src/lib.rs

@@ -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)]

+ 2 - 0
banks-server/src/banks_server.rs

@@ -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,
                 }),
             };
         }

+ 1 - 0
cli-output/Cargo.toml

@@ -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" }

+ 35 - 0
cli-output/src/display.rs

@@ -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

+ 2 - 0
client/src/mock_sender.rs

@@ -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],

+ 2 - 0
client/src/rpc_response.rs

@@ -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)]

+ 2 - 0
core/src/banking_stage.rs

@@ -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,
         })
     }
 

+ 3 - 3
core/src/validator.rs

@@ -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,
     ));
 

+ 34 - 0
ledger/src/blockstore.rs

@@ -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();

+ 2 - 0
ledger/src/blockstore_processor.rs

@@ -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();

+ 3 - 0
multinode-demo/bootstrap-validator.sh

@@ -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

+ 3 - 0
multinode-demo/validator.sh

@@ -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

+ 2 - 2
net/remote/remote-node.sh

@@ -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

+ 1 - 0
program-test/Cargo.toml

@@ -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"

+ 57 - 0
program-test/tests/return_data.rs

@@ -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
+    );
+}

+ 8 - 0
programs/bpf/Cargo.lock

@@ -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",

+ 4 - 0
programs/bpf/tests/programs.rs

@@ -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 {

+ 11 - 2
rpc/src/rpc.rs

@@ -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(),
             )
         );
 

+ 9 - 4
rpc/src/transaction_status_service.rs

@@ -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 {

+ 1 - 0
runtime/src/accounts.rs

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

+ 131 - 7
runtime/src/bank.rs

@@ -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, .. } =

+ 1 - 1
scripts/run.sh

@@ -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

+ 35 - 19
sdk/src/transaction_context.rs

@@ -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,
+        }
+    }
+}

+ 3 - 1
storage-bigtable/src/bigtable.rs

@@ -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 {

+ 1 - 0
storage-bigtable/src/lib.rs

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

+ 7 - 0
storage-proto/proto/confirmed_block.proto

@@ -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;

+ 32 - 0
storage-proto/src/convert.rs

@@ -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 {

+ 7 - 0
storage-proto/src/lib.rs

@@ -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,
         })
     }
 }

+ 6 - 0
transaction-status/src/lib.rs

@@ -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,
         }
     }
 }

+ 1 - 1
validator/src/bin/solana-test-validator.rs

@@ -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()

+ 23 - 4
validator/src/main.rs

@@ -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")