Store address table lookups in blockstore and bigtable (#22402)

This commit is contained in:
Justin Starry 2022-01-14 15:24:41 +08:00 committed by GitHub
parent 4c577d7f8c
commit f804ccdece
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 836 additions and 199 deletions

1
Cargo.lock generated
View File

@ -4848,6 +4848,7 @@ dependencies = [
"serde_json",
"serial_test",
"solana-accountsdb-plugin-manager",
"solana-address-lookup-table-program",
"solana-client",
"solana-entry",
"solana-frozen-abi 1.10.0",

View File

@ -1034,6 +1034,10 @@ pub(crate) mod tests {
commission: Some(11),
},
]),
loaded_addresses: LoadedAddresses {
writable: vec![Pubkey::new_unique()],
readonly: vec![Pubkey::new_unique()],
},
}
}

View File

@ -275,8 +275,9 @@ fn test_block_subscription() {
let maybe_actual = receiver.recv_timeout(Duration::from_millis(400));
match maybe_actual {
Ok(actual) => {
let complete_block = blockstore.get_complete_block(slot, false).unwrap();
let block = complete_block.clone().configure(
let versioned_block = blockstore.get_complete_block(slot, false).unwrap();
let legacy_block = versioned_block.into_legacy_block().unwrap();
let block = legacy_block.clone().configure(
UiTransactionEncoding::Json,
TransactionDetails::Signatures,
false,
@ -286,7 +287,7 @@ fn test_block_subscription() {
block: Some(block),
err: None,
};
let block = complete_block.configure(
let block = legacy_block.configure(
UiTransactionEncoding::Json,
TransactionDetails::Signatures,
false,

View File

@ -20,6 +20,7 @@ pub const JSON_RPC_SERVER_ERROR_TRANSACTION_HISTORY_NOT_AVAILABLE: i64 = -32011;
pub const JSON_RPC_SCAN_ERROR: i64 = -32012;
pub const JSON_RPC_SERVER_ERROR_TRANSACTION_SIGNATURE_LEN_MISMATCH: i64 = -32013;
pub const JSON_RPC_SERVER_ERROR_BLOCK_STATUS_NOT_AVAILABLE_YET: i64 = -32014;
pub const JSON_RPC_SERVER_ERROR_UNSUPPORTED_TRANSACTION_VERSION: i64 = -32015;
#[derive(Error, Debug)]
pub enum RpcCustomError {
@ -57,6 +58,8 @@ pub enum RpcCustomError {
TransactionSignatureLenMismatch,
#[error("BlockStatusNotAvailableYet")]
BlockStatusNotAvailableYet { slot: Slot },
#[error("UnsupportedTransactionVersion")]
UnsupportedTransactionVersion,
}
#[derive(Debug, Serialize, Deserialize)]
@ -169,6 +172,11 @@ impl From<RpcCustomError> for Error {
message: format!("Block status not yet available for slot {}", slot),
data: None,
},
RpcCustomError::UnsupportedTransactionVersion => Self {
code: ErrorCode::ServerError(JSON_RPC_SERVER_ERROR_UNSUPPORTED_TRANSACTION_VERSION),
message: "Versioned transactions are not supported".to_string(),
data: None,
},
}
}
}

View File

@ -429,6 +429,9 @@ pub struct RpcInflationReward {
pub enum RpcBlockUpdateError {
#[error("block store error")]
BlockStoreError,
#[error("unsupported transaction version")]
UnsupportedTransactionVersion,
}
#[derive(Serialize, Deserialize, Debug)]

View File

@ -33,6 +33,7 @@ rayon = "1.5.1"
retain_mut = "0.1.5"
serde = "1.0.133"
serde_derive = "1.0.103"
solana-address-lookup-table-program = { path = "../programs/address-lookup-table", version = "=1.10.0" }
solana-accountsdb-plugin-manager = { path = "../accountsdb-plugin-manager", version = "=1.10.0" }
solana-client = { path = "../client", version = "=1.10.0" }
solana-entry = { path = "../entry", version = "=1.10.0" }

View File

@ -1477,7 +1477,8 @@ mod tests {
super::*,
crossbeam_channel::{unbounded, Receiver},
itertools::Itertools,
solana_entry::entry::{next_entry, Entry, EntrySlice},
solana_address_lookup_table_program::state::{AddressLookupTable, LookupTableMeta},
solana_entry::entry::{next_entry, next_versioned_entry, Entry, EntrySlice},
solana_gossip::{cluster_info::Node, contact_info::ContactInfo},
solana_ledger::{
blockstore::{entries_to_test_shreds, Blockstore},
@ -1493,8 +1494,10 @@ mod tests {
solana_rpc::transaction_status_service::TransactionStatusService,
solana_runtime::bank::TransactionExecutionDetails,
solana_sdk::{
account::AccountSharedData,
hash::Hash,
instruction::InstructionError,
message::{v0, MessageHeader, VersionedMessage},
poh_config::PohConfig,
signature::{Keypair, Signer},
system_instruction::SystemError,
@ -1502,9 +1505,10 @@ mod tests {
transaction::{Transaction, TransactionError},
},
solana_streamer::{recvmmsg::recv_mmsg, socket::SocketAddrSpace},
solana_transaction_status::TransactionWithStatusMeta,
solana_transaction_status::{TransactionStatusMeta, VersionedTransactionWithStatusMeta},
solana_vote_program::vote_transaction,
std::{
borrow::Cow,
net::SocketAddr,
path::Path,
sync::atomic::{AtomicBool, Ordering},
@ -2525,7 +2529,7 @@ mod tests {
let confirmed_block = blockstore.get_rooted_block(bank.slot(), false).unwrap();
assert_eq!(confirmed_block.transactions.len(), 3);
for TransactionWithStatusMeta { transaction, meta } in
for VersionedTransactionWithStatusMeta { transaction, meta } in
confirmed_block.transactions.into_iter()
{
if transaction.signatures[0] == success_signature {
@ -2555,6 +2559,165 @@ mod tests {
Blockstore::destroy(&ledger_path).unwrap();
}
fn generate_new_address_lookup_table(
authority: Option<Pubkey>,
num_addresses: usize,
) -> AddressLookupTable<'static> {
let mut addresses = Vec::with_capacity(num_addresses);
addresses.resize_with(num_addresses, Pubkey::new_unique);
AddressLookupTable {
meta: LookupTableMeta {
authority,
..LookupTableMeta::default()
},
addresses: Cow::Owned(addresses),
}
}
fn store_address_lookup_table(
bank: &Bank,
account_address: Pubkey,
address_lookup_table: AddressLookupTable<'static>,
) -> AccountSharedData {
let mut data = Vec::new();
address_lookup_table.serialize_for_tests(&mut data).unwrap();
let mut account =
AccountSharedData::new(1, data.len(), &solana_address_lookup_table_program::id());
account.set_data(data);
bank.store_account(&account_address, &account);
account
}
#[test]
fn test_write_persist_loaded_addresses() {
solana_logger::setup();
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_slow_genesis_config(10_000);
let bank = Arc::new(Bank::new_no_wallclock_throttle_for_tests(&genesis_config));
let keypair = Keypair::new();
let address_table_key = Pubkey::new_unique();
let address_table_state = generate_new_address_lookup_table(None, 2);
store_address_lookup_table(&bank, address_table_key, address_table_state);
let bank = Arc::new(Bank::new_from_parent(&bank, &Pubkey::new_unique(), 1));
let message = VersionedMessage::V0(v0::Message {
header: MessageHeader {
num_required_signatures: 1,
num_readonly_signed_accounts: 0,
num_readonly_unsigned_accounts: 0,
},
recent_blockhash: genesis_config.hash(),
account_keys: vec![keypair.pubkey()],
address_table_lookups: vec![MessageAddressTableLookup {
account_key: address_table_key,
writable_indexes: vec![0],
readonly_indexes: vec![1],
}],
instructions: vec![],
});
let tx = VersionedTransaction::try_new(message, &[&keypair]).unwrap();
let message_hash = tx.message.hash();
let sanitized_tx =
SanitizedTransaction::try_create(tx.clone(), message_hash, Some(false), |lookups| {
Ok(bank.load_lookup_table_addresses(lookups).unwrap())
})
.unwrap();
let entry = next_versioned_entry(&genesis_config.hash(), 1, vec![tx]);
let entries = vec![entry];
// todo: check if sig fees are needed
bank.transfer(1, &mint_keypair, &keypair.pubkey()).unwrap();
let ledger_path = get_tmp_ledger_path!();
{
let blockstore = Blockstore::open(&ledger_path)
.expect("Expected to be able to open database ledger");
let blockstore = Arc::new(blockstore);
let (poh_recorder, _entry_receiver, record_receiver) = PohRecorder::new(
bank.tick_height(),
bank.last_blockhash(),
bank.clone(),
Some((4, 4)),
bank.ticks_per_slot(),
&Pubkey::new_unique(),
&blockstore,
&Arc::new(LeaderScheduleCache::new_from_bank(&bank)),
&Arc::new(PohConfig::default()),
Arc::new(AtomicBool::default()),
);
let recorder = poh_recorder.recorder();
let poh_recorder = Arc::new(Mutex::new(poh_recorder));
let poh_simulator = simulate_poh(record_receiver, &poh_recorder);
poh_recorder.lock().unwrap().set_bank(&bank);
let shreds = entries_to_test_shreds(&entries, bank.slot(), 0, true, 0);
blockstore.insert_shreds(shreds, None, false).unwrap();
blockstore.set_roots(std::iter::once(&bank.slot())).unwrap();
let (transaction_status_sender, transaction_status_receiver) = unbounded();
let transaction_status_service = TransactionStatusService::new(
transaction_status_receiver,
Arc::new(AtomicU64::default()),
true,
None,
blockstore.clone(),
&Arc::new(AtomicBool::new(false)),
);
let (gossip_vote_sender, _gossip_vote_receiver) = unbounded();
let _ = BankingStage::process_and_record_transactions(
&bank,
&[sanitized_tx.clone()],
&recorder,
0,
Some(TransactionStatusSender {
sender: transaction_status_sender,
enable_cpi_and_log_storage: false,
}),
&gossip_vote_sender,
&QosService::new(Arc::new(RwLock::new(CostModel::default())), 1),
);
transaction_status_service.join().unwrap();
let mut confirmed_block = blockstore.get_rooted_block(bank.slot(), false).unwrap();
assert_eq!(confirmed_block.transactions.len(), 1);
let recorded_meta = confirmed_block.transactions.pop().unwrap().meta;
assert_eq!(
recorded_meta,
Some(TransactionStatusMeta {
status: Ok(()),
pre_balances: vec![1, 0, 0],
post_balances: vec![1, 0, 0],
pre_token_balances: Some(vec![]),
post_token_balances: Some(vec![]),
rewards: Some(vec![]),
loaded_addresses: sanitized_tx.get_loaded_addresses(),
..TransactionStatusMeta::default()
})
);
poh_recorder
.lock()
.unwrap()
.is_exited
.store(true, Ordering::Relaxed);
let _ = poh_simulator.join();
}
Blockstore::destroy(&ledger_path).unwrap();
}
#[allow(clippy::type_complexity)]
fn setup_conflicting_transactions(
ledger_path: &Path,

View File

@ -3003,7 +3003,7 @@ pub mod tests {
transaction::TransactionError,
},
solana_streamer::socket::SocketAddrSpace,
solana_transaction_status::TransactionWithStatusMeta,
solana_transaction_status::VersionedTransactionWithStatusMeta,
solana_vote_program::{
vote_state::{VoteState, VoteStateVersions},
vote_transaction,
@ -3860,7 +3860,7 @@ pub mod tests {
let confirmed_block = blockstore.get_rooted_block(slot, false).unwrap();
assert_eq!(confirmed_block.transactions.len(), 3);
for TransactionWithStatusMeta { transaction, meta } in
for VersionedTransactionWithStatusMeta { transaction, meta } in
confirmed_block.transactions.into_iter()
{
if transaction.signatures[0] == signatures[0] {

View File

@ -898,8 +898,17 @@ pub fn create_random_ticks(num_ticks: u64, max_hashes_per_tick: u64, mut hash: H
/// Creates the next Tick or Transaction Entry `num_hashes` after `start_hash`.
pub fn next_entry(prev_hash: &Hash, num_hashes: u64, transactions: Vec<Transaction>) -> Entry {
assert!(num_hashes > 0 || transactions.is_empty());
let transactions = transactions.into_iter().map(Into::into).collect::<Vec<_>>();
next_versioned_entry(prev_hash, num_hashes, transactions)
}
/// Creates the next Tick or Transaction Entry `num_hashes` after `start_hash`.
pub fn next_versioned_entry(
prev_hash: &Hash,
num_hashes: u64,
transactions: Vec<VersionedTransaction>,
) -> Entry {
assert!(num_hashes > 0 || transactions.is_empty());
Entry {
num_hashes,
hash: next_hash(prev_hash, num_hashes, &transactions),

View File

@ -73,7 +73,10 @@ async fn block(slot: Slot, output_format: OutputFormat) -> Result<(), Box<dyn st
.await
.map_err(|err| format!("Failed to connect to storage: {:?}", err))?;
let block = bigtable.get_confirmed_block(slot).await?;
let versioned_block = bigtable.get_confirmed_block(slot).await?;
let block = versioned_block
.into_legacy_block()
.ok_or_else(|| "Failed to read versioned transaction in block".to_string())?;
let cli_block = CliBlock {
encoded_confirmed_block: block.encode(UiTransactionEncoding::Base64),
@ -153,7 +156,11 @@ async fn confirm(
let mut get_transaction_error = None;
if verbose {
match bigtable.get_confirmed_transaction(signature).await {
Ok(Some(confirmed_transaction)) => {
Ok(Some(versioned_confirmed_tx)) => {
let confirmed_transaction = versioned_confirmed_tx
.into_legacy_confirmed_transaction()
.ok_or_else(|| "Failed to read versioned transaction in block".to_string())?;
transaction = Some(CliTransaction {
transaction: confirmed_transaction
.transaction
@ -260,7 +267,10 @@ pub async fn transaction_history(
println!(" Unable to get confirmed transaction details: {}", err);
break;
}
Ok(block) => {
Ok(versioned_block) => {
let block = versioned_block.into_legacy_block().ok_or_else(|| {
"Failed to read versioned transaction in block".to_string()
})?;
loaded_block = Some((result.slot, block));
}
}

View File

@ -42,9 +42,9 @@ use {
},
solana_storage_proto::{StoredExtendedRewards, StoredTransactionStatusMeta},
solana_transaction_status::{
ConfirmedBlock, ConfirmedTransactionStatusWithSignature,
ConfirmedTransactionWithStatusMeta, Rewards, TransactionStatusMeta,
TransactionWithStatusMeta,
ConfirmedTransactionStatusWithSignature, Rewards, TransactionStatusMeta,
VersionedConfirmedBlock, VersionedConfirmedTransactionWithStatusMeta,
VersionedTransactionWithStatusMeta,
},
std::{
borrow::Cow,
@ -1923,7 +1923,7 @@ impl Blockstore {
&self,
slot: Slot,
require_previous_blockhash: bool,
) -> Result<ConfirmedBlock> {
) -> Result<VersionedConfirmedBlock> {
datapoint_info!(
"blockstore-rpc-api",
("method", "get_rooted_block".to_string(), String)
@ -1940,7 +1940,7 @@ impl Blockstore {
&self,
slot: Slot,
require_previous_blockhash: bool,
) -> Result<ConfirmedBlock> {
) -> Result<VersionedConfirmedBlock> {
let slot_meta_cf = self.db.column::<cf::SlotMeta>();
let slot_meta = match slot_meta_cf.get(slot)? {
Some(slot_meta) => slot_meta,
@ -1998,7 +1998,7 @@ impl Blockstore {
let block_time = self.blocktime_cf.get(slot)?;
let block_height = self.block_height_cf.get(slot)?;
let block = ConfirmedBlock {
let block = VersionedConfirmedBlock {
previous_blockhash: previous_blockhash.to_string(),
blockhash: blockhash.to_string(),
// If the slot is full it should have parent_slot populated
@ -2020,22 +2020,17 @@ impl Blockstore {
&self,
slot: Slot,
iterator: impl Iterator<Item = VersionedTransaction>,
) -> Result<Vec<TransactionWithStatusMeta>> {
) -> Result<Vec<VersionedTransactionWithStatusMeta>> {
iterator
.map(|versioned_tx| {
// TODO: add support for versioned transactions
if let Some(transaction) = versioned_tx.into_legacy_transaction() {
.map(|transaction| {
let signature = transaction.signatures[0];
Ok(TransactionWithStatusMeta {
Ok(VersionedTransactionWithStatusMeta {
transaction,
meta: self
.read_transaction_status((signature, slot))
.ok()
.flatten(),
})
} else {
Err(BlockstoreError::UnsupportedTransactionVersion)
}
})
.collect()
}
@ -2287,7 +2282,7 @@ impl Blockstore {
pub fn get_rooted_transaction(
&self,
signature: Signature,
) -> Result<Option<ConfirmedTransactionWithStatusMeta>> {
) -> Result<Option<VersionedConfirmedTransactionWithStatusMeta>> {
datapoint_info!(
"blockstore-rpc-api",
("method", "get_rooted_transaction".to_string(), String)
@ -2300,7 +2295,7 @@ impl Blockstore {
&self,
signature: Signature,
highest_confirmed_slot: Slot,
) -> Result<Option<ConfirmedTransactionWithStatusMeta>> {
) -> Result<Option<VersionedConfirmedTransactionWithStatusMeta>> {
datapoint_info!(
"blockstore-rpc-api",
("method", "get_complete_transaction".to_string(), String)
@ -2317,7 +2312,7 @@ impl Blockstore {
&self,
signature: Signature,
confirmed_unrooted_slots: &[Slot],
) -> Result<Option<ConfirmedTransactionWithStatusMeta>> {
) -> Result<Option<VersionedConfirmedTransactionWithStatusMeta>> {
if let Some((slot, status)) =
self.get_transaction_status(signature, confirmed_unrooted_slots)?
{
@ -2325,15 +2320,10 @@ impl Blockstore {
.find_transaction_in_slot(slot, signature)?
.ok_or(BlockstoreError::TransactionStatusSlotMismatch)?; // Should not happen
// TODO: support retrieving versioned transactions
let transaction = transaction
.into_legacy_transaction()
.ok_or(BlockstoreError::UnsupportedTransactionVersion)?;
let block_time = self.get_block_time(slot)?;
Ok(Some(ConfirmedTransactionWithStatusMeta {
Ok(Some(VersionedConfirmedTransactionWithStatusMeta {
slot,
transaction: TransactionWithStatusMeta {
tx_with_meta: VersionedTransactionWithStatusMeta {
transaction,
meta: Some(status),
},
@ -4146,6 +4136,7 @@ pub mod tests {
solana_sdk::{
hash::{self, hash, Hash},
instruction::CompiledInstruction,
message::v0::LoadedAddresses,
packet::PACKET_DATA_SIZE,
pubkey::Pubkey,
signature::Signature,
@ -6237,20 +6228,15 @@ pub mod tests {
.put_meta_bytes(slot - 1, &serialize(&parent_meta).unwrap())
.unwrap();
let expected_transactions: Vec<TransactionWithStatusMeta> = entries
let expected_transactions: Vec<VersionedTransactionWithStatusMeta> = entries
.iter()
.cloned()
.filter(|entry| !entry.is_tick())
.flat_map(|entry| entry.transactions)
.map(|transaction| {
transaction
.into_legacy_transaction()
.expect("versioned transactions not supported")
})
.map(|transaction| {
let mut pre_balances: Vec<u64> = vec![];
let mut post_balances: Vec<u64> = vec![];
for (i, _account_key) in transaction.message.account_keys.iter().enumerate() {
for i in 0..transaction.message.total_account_keys_len() {
pre_balances.push(i as u64 * 10);
post_balances.push(i as u64 * 11);
}
@ -6265,6 +6251,7 @@ pub mod tests {
pre_token_balances: Some(vec![]),
post_token_balances: Some(vec![]),
rewards: Some(vec![]),
loaded_addresses: LoadedAddresses::default(),
}
.into();
blockstore
@ -6281,6 +6268,7 @@ pub mod tests {
pre_token_balances: Some(vec![]),
post_token_balances: Some(vec![]),
rewards: Some(vec![]),
loaded_addresses: LoadedAddresses::default(),
}
.into();
blockstore
@ -6297,13 +6285,14 @@ pub mod tests {
pre_token_balances: Some(vec![]),
post_token_balances: Some(vec![]),
rewards: Some(vec![]),
loaded_addresses: LoadedAddresses::default(),
}
.into();
blockstore
.transaction_status_cf
.put_protobuf((0, signature, slot + 2), &status)
.unwrap();
TransactionWithStatusMeta {
VersionedTransactionWithStatusMeta {
transaction,
meta: Some(TransactionStatusMeta {
status: Ok(()),
@ -6315,6 +6304,7 @@ pub mod tests {
pre_token_balances: Some(vec![]),
post_token_balances: Some(vec![]),
rewards: Some(vec![]),
loaded_addresses: LoadedAddresses::default(),
}),
}
})
@ -6335,7 +6325,7 @@ pub mod tests {
// Test if require_previous_blockhash is false
let confirmed_block = blockstore.get_rooted_block(slot, false).unwrap();
assert_eq!(confirmed_block.transactions.len(), 100);
let expected_block = ConfirmedBlock {
let expected_block = VersionedConfirmedBlock {
transactions: expected_transactions.clone(),
parent_slot: slot - 1,
blockhash: blockhash.to_string(),
@ -6349,7 +6339,7 @@ pub mod tests {
let confirmed_block = blockstore.get_rooted_block(slot + 1, true).unwrap();
assert_eq!(confirmed_block.transactions.len(), 100);
let mut expected_block = ConfirmedBlock {
let mut expected_block = VersionedConfirmedBlock {
transactions: expected_transactions.clone(),
parent_slot: slot,
blockhash: blockhash.to_string(),
@ -6366,7 +6356,7 @@ pub mod tests {
let complete_block = blockstore.get_complete_block(slot + 2, true).unwrap();
assert_eq!(complete_block.transactions.len(), 100);
let mut expected_complete_block = ConfirmedBlock {
let mut expected_complete_block = VersionedConfirmedBlock {
transactions: expected_transactions,
parent_slot: slot + 1,
blockhash: blockhash.to_string(),
@ -6422,6 +6412,10 @@ pub mod tests {
let pre_token_balances_vec = vec![];
let post_token_balances_vec = vec![];
let rewards_vec = vec![];
let test_loaded_addresses = LoadedAddresses {
writable: vec![Pubkey::new_unique()],
readonly: vec![Pubkey::new_unique()],
};
// result not found
assert!(transaction_status_cf
@ -6440,6 +6434,7 @@ pub mod tests {
pre_token_balances: Some(pre_token_balances_vec.clone()),
post_token_balances: Some(post_token_balances_vec.clone()),
rewards: Some(rewards_vec.clone()),
loaded_addresses: test_loaded_addresses.clone(),
}
.into();
assert!(transaction_status_cf
@ -6457,6 +6452,7 @@ pub mod tests {
pre_token_balances,
post_token_balances,
rewards,
loaded_addresses,
} = transaction_status_cf
.get_protobuf_or_bincode::<StoredTransactionStatusMeta>((0, Signature::default(), 0))
.unwrap()
@ -6472,6 +6468,7 @@ pub mod tests {
assert_eq!(pre_token_balances.unwrap(), pre_token_balances_vec);
assert_eq!(post_token_balances.unwrap(), post_token_balances_vec);
assert_eq!(rewards.unwrap(), rewards_vec);
assert_eq!(loaded_addresses, test_loaded_addresses);
// insert value
let status = TransactionStatusMeta {
@ -6484,6 +6481,7 @@ pub mod tests {
pre_token_balances: Some(pre_token_balances_vec.clone()),
post_token_balances: Some(post_token_balances_vec.clone()),
rewards: Some(rewards_vec.clone()),
loaded_addresses: test_loaded_addresses.clone(),
}
.into();
assert!(transaction_status_cf
@ -6501,6 +6499,7 @@ pub mod tests {
pre_token_balances,
post_token_balances,
rewards,
loaded_addresses,
} = transaction_status_cf
.get_protobuf_or_bincode::<StoredTransactionStatusMeta>((
0,
@ -6522,6 +6521,7 @@ pub mod tests {
assert_eq!(pre_token_balances.unwrap(), pre_token_balances_vec);
assert_eq!(post_token_balances.unwrap(), post_token_balances_vec);
assert_eq!(rewards.unwrap(), rewards_vec);
assert_eq!(loaded_addresses, test_loaded_addresses);
}
#[test]
@ -6749,6 +6749,7 @@ pub mod tests {
pre_token_balances: Some(vec![]),
post_token_balances: Some(vec![]),
rewards: Some(vec![]),
loaded_addresses: LoadedAddresses::default(),
}
.into();
@ -6943,6 +6944,7 @@ pub mod tests {
pre_token_balances: Some(vec![]),
post_token_balances: Some(vec![]),
rewards: Some(vec![]),
loaded_addresses: LoadedAddresses::default(),
}
.into();
@ -7093,19 +7095,15 @@ pub mod tests {
blockstore.insert_shreds(shreds, None, false).unwrap();
blockstore.set_roots(vec![slot - 1, slot].iter()).unwrap();
let expected_transactions: Vec<TransactionWithStatusMeta> = entries
let expected_transactions: Vec<VersionedTransactionWithStatusMeta> = entries
.iter()
.cloned()
.filter(|entry| !entry.is_tick())
.flat_map(|entry| entry.transactions)
.map(|tx| {
tx.into_legacy_transaction()
.expect("versioned transactions not supported")
})
.map(|transaction| {
let mut pre_balances: Vec<u64> = vec![];
let mut post_balances: Vec<u64> = vec![];
for (i, _account_key) in transaction.message.account_keys.iter().enumerate() {
for i in 0..transaction.message.total_account_keys_len() {
pre_balances.push(i as u64 * 10);
post_balances.push(i as u64 * 11);
}
@ -7128,13 +7126,14 @@ pub mod tests {
pre_token_balances: pre_token_balances.clone(),
post_token_balances: post_token_balances.clone(),
rewards: rewards.clone(),
loaded_addresses: LoadedAddresses::default(),
}
.into();
blockstore
.transaction_status_cf
.put_protobuf((0, signature, slot), &status)
.unwrap();
TransactionWithStatusMeta {
VersionedTransactionWithStatusMeta {
transaction,
meta: Some(TransactionStatusMeta {
status: Ok(()),
@ -7146,18 +7145,19 @@ pub mod tests {
pre_token_balances,
post_token_balances,
rewards,
loaded_addresses: LoadedAddresses::default(),
}),
}
})
.collect();
for transaction in expected_transactions.clone() {
let signature = transaction.transaction.signatures[0];
for tx_with_meta in expected_transactions.clone() {
let signature = tx_with_meta.transaction.signatures[0];
assert_eq!(
blockstore.get_rooted_transaction(signature).unwrap(),
Some(ConfirmedTransactionWithStatusMeta {
Some(VersionedConfirmedTransactionWithStatusMeta {
slot,
transaction: transaction.clone(),
tx_with_meta: tx_with_meta.clone(),
block_time: None
})
);
@ -7165,9 +7165,9 @@ pub mod tests {
blockstore
.get_complete_transaction(signature, slot + 1)
.unwrap(),
Some(ConfirmedTransactionWithStatusMeta {
Some(VersionedConfirmedTransactionWithStatusMeta {
slot,
transaction,
tx_with_meta,
block_time: None
})
);
@ -7175,7 +7175,7 @@ pub mod tests {
blockstore.run_purge(0, 2, PurgeType::PrimaryIndex).unwrap();
*blockstore.lowest_cleanup_slot.write().unwrap() = slot;
for TransactionWithStatusMeta { transaction, .. } in expected_transactions {
for VersionedTransactionWithStatusMeta { transaction, .. } in expected_transactions {
let signature = transaction.signatures[0];
assert_eq!(blockstore.get_rooted_transaction(signature).unwrap(), None,);
assert_eq!(
@ -7197,19 +7197,15 @@ pub mod tests {
let shreds = entries_to_test_shreds(&entries, slot, slot - 1, true, 0);
blockstore.insert_shreds(shreds, None, false).unwrap();
let expected_transactions: Vec<TransactionWithStatusMeta> = entries
let expected_transactions: Vec<VersionedTransactionWithStatusMeta> = entries
.iter()
.cloned()
.filter(|entry| !entry.is_tick())
.flat_map(|entry| entry.transactions)
.map(|tx| {
tx.into_legacy_transaction()
.expect("versioned transactions not supported")
})
.map(|transaction| {
let mut pre_balances: Vec<u64> = vec![];
let mut post_balances: Vec<u64> = vec![];
for (i, _account_key) in transaction.message.account_keys.iter().enumerate() {
for i in 0..transaction.message.total_account_keys_len() {
pre_balances.push(i as u64 * 10);
post_balances.push(i as u64 * 11);
}
@ -7232,13 +7228,14 @@ pub mod tests {
pre_token_balances: pre_token_balances.clone(),
post_token_balances: post_token_balances.clone(),
rewards: rewards.clone(),
loaded_addresses: LoadedAddresses::default(),
}
.into();
blockstore
.transaction_status_cf
.put_protobuf((0, signature, slot), &status)
.unwrap();
TransactionWithStatusMeta {
VersionedTransactionWithStatusMeta {
transaction,
meta: Some(TransactionStatusMeta {
status: Ok(()),
@ -7250,20 +7247,21 @@ pub mod tests {
pre_token_balances,
post_token_balances,
rewards,
loaded_addresses: LoadedAddresses::default(),
}),
}
})
.collect();
for transaction in expected_transactions.clone() {
let signature = transaction.transaction.signatures[0];
for tx_with_meta in expected_transactions.clone() {
let signature = tx_with_meta.transaction.signatures[0];
assert_eq!(
blockstore
.get_complete_transaction(signature, slot)
.unwrap(),
Some(ConfirmedTransactionWithStatusMeta {
Some(VersionedConfirmedTransactionWithStatusMeta {
slot,
transaction,
tx_with_meta,
block_time: None
})
);
@ -7272,7 +7270,7 @@ pub mod tests {
blockstore.run_purge(0, 2, PurgeType::PrimaryIndex).unwrap();
*blockstore.lowest_cleanup_slot.write().unwrap() = slot;
for TransactionWithStatusMeta { transaction, .. } in expected_transactions {
for VersionedTransactionWithStatusMeta { transaction, .. } in expected_transactions {
let signature = transaction.signatures[0];
assert_eq!(
blockstore
@ -7560,16 +7558,13 @@ pub mod tests {
// Purge to freeze index 0 and write address-signatures in new primary index
blockstore.run_purge(0, 1, PurgeType::PrimaryIndex).unwrap();
}
for tx in entry.transactions {
let transaction = tx
.into_legacy_transaction()
.expect("versioned transactions not supported");
for transaction in entry.transactions {
assert_eq!(transaction.signatures.len(), 1);
blockstore
.write_transaction_status(
slot,
transaction.signatures[0],
transaction.message.account_keys.iter().collect(),
transaction.message.static_account_keys_iter().collect(),
vec![],
TransactionStatusMeta::default(),
)
@ -7587,16 +7582,13 @@ pub mod tests {
blockstore.insert_shreds(shreds, None, false).unwrap();
for entry in entries.into_iter() {
for tx in entry.transactions {
let transaction = tx
.into_legacy_transaction()
.expect("versioned transactions not supported");
for transaction in entry.transactions {
assert_eq!(transaction.signatures.len(), 1);
blockstore
.write_transaction_status(
slot,
transaction.signatures[0],
transaction.message.account_keys.iter().collect(),
transaction.message.static_account_keys_iter().collect(),
vec![],
TransactionStatusMeta::default(),
)
@ -8019,6 +8011,7 @@ pub mod tests {
pre_token_balances: Some(vec![]),
post_token_balances: Some(vec![]),
rewards: Some(vec![]),
loaded_addresses: LoadedAddresses::default(),
}
.into();
transaction_status_cf
@ -8570,8 +8563,9 @@ pub mod tests {
reward_type: Some(RewardType::Rent),
commission: None,
}]),
loaded_addresses: LoadedAddresses::default(),
};
let deprecated_status: StoredTransactionStatusMeta = status.clone().into();
let deprecated_status: StoredTransactionStatusMeta = status.clone().try_into().unwrap();
let protobuf_status: generated::TransactionStatusMeta = status.into();
for slot in 0..2 {

View File

@ -327,19 +327,24 @@ impl Blockstore {
let mut index1 = self.transaction_status_index_cf.get(1)?.unwrap_or_default();
for slot in from_slot..to_slot {
let slot_entries = self.get_any_valid_slot_entries(slot, 0);
for transaction in slot_entries
.iter()
.cloned()
.flat_map(|entry| entry.transactions)
{
let transactions = slot_entries
.into_iter()
.flat_map(|entry| entry.transactions);
for transaction in transactions {
if let Some(&signature) = transaction.signatures.get(0) {
batch.delete::<cf::TransactionStatus>((0, signature, slot))?;
batch.delete::<cf::TransactionStatus>((1, signature, slot))?;
// TODO: support purging dynamically loaded addresses from versioned transactions
for pubkey in transaction.message.into_static_account_keys() {
batch.delete::<cf::AddressSignatures>((0, pubkey, slot, signature))?;
batch.delete::<cf::AddressSignatures>((1, pubkey, slot, signature))?;
}
let meta = self.read_transaction_status((signature, slot))?;
let loaded_addresses =
meta.map(|meta| meta.loaded_addresses).unwrap_or_default();
for address in loaded_addresses.into_ordered_iter() {
batch.delete::<cf::AddressSignatures>((0, address, slot, signature))?;
batch.delete::<cf::AddressSignatures>((1, address, slot, signature))?;
}
}
}
}

View File

@ -45,7 +45,7 @@ use solana_sdk::{
entrypoint::{MAX_PERMITTED_DATA_INCREASE, SUCCESS},
instruction::{AccountMeta, CompiledInstruction, Instruction, InstructionError},
loader_instruction,
message::{Message, SanitizedMessage},
message::{v0::LoadedAddresses, Message, SanitizedMessage},
pubkey::Pubkey,
signature::{keypair_from_seed, Keypair, Signer},
system_instruction::{self, MAX_PERMITTED_DATA_LENGTH},
@ -421,6 +421,7 @@ fn execute_transactions(
inner_instructions,
log_messages,
rewards: None,
loaded_addresses: LoadedAddresses::default(),
};
Ok(ConfirmedTransactionWithStatusMeta {

View File

@ -77,9 +77,10 @@ use {
solana_storage_bigtable::Error as StorageError,
solana_streamer::socket::SocketAddrSpace,
solana_transaction_status::{
ConfirmedBlock, ConfirmedTransactionStatusWithSignature, Encodable,
ConfirmedTransactionStatusWithSignature, Encodable,
EncodedConfirmedTransactionWithStatusMeta, Reward, RewardType,
TransactionConfirmationStatus, TransactionStatus, UiConfirmedBlock, UiTransactionEncoding,
VersionedConfirmedBlock,
},
solana_vote_program::vote_state::{VoteState, MAX_LOCKOUT_HISTORY},
spl_token::{
@ -1002,32 +1003,40 @@ impl JsonRpcRequestProcessor {
self.check_status_is_complete(slot)?;
let result = self.blockstore.get_rooted_block(slot, true);
self.check_blockstore_root(&result, slot)?;
let configure_block = |confirmed_block: ConfirmedBlock| {
let configure_block = |versioned_block: VersionedConfirmedBlock| {
let confirmed_block = versioned_block
.into_legacy_block()
.ok_or(RpcCustomError::UnsupportedTransactionVersion)?;
let mut confirmed_block =
confirmed_block.configure(encoding, transaction_details, show_rewards);
if slot == 0 {
confirmed_block.block_time = Some(self.genesis_creation_time());
confirmed_block.block_height = Some(0);
}
confirmed_block
Ok(confirmed_block)
};
if result.is_err() {
if let Some(bigtable_ledger_storage) = &self.bigtable_ledger_storage {
let bigtable_result =
bigtable_ledger_storage.get_confirmed_block(slot).await;
self.check_bigtable_result(&bigtable_result)?;
return Ok(bigtable_result.ok().map(configure_block));
return bigtable_result.ok().map(configure_block).transpose();
}
}
self.check_slot_cleaned_up(&result, slot)?;
return Ok(result.ok().map(configure_block));
return result.ok().map(configure_block).transpose();
} else if commitment.is_confirmed() {
// Check if block is confirmed
let confirmed_bank = self.bank(Some(CommitmentConfig::confirmed()));
if confirmed_bank.status_cache_ancestors().contains(&slot) {
self.check_status_is_complete(slot)?;
let result = self.blockstore.get_complete_block(slot, true);
return Ok(result.ok().map(|mut confirmed_block| {
return result
.ok()
.map(|versioned_block| {
let mut confirmed_block = versioned_block
.into_legacy_block()
.ok_or(RpcCustomError::UnsupportedTransactionVersion)?;
if confirmed_block.block_time.is_none()
|| confirmed_block.block_height.is_none()
{
@ -1035,15 +1044,21 @@ impl JsonRpcRequestProcessor {
let bank = r_bank_forks.get(slot).cloned();
if let Some(bank) = bank {
if confirmed_block.block_time.is_none() {
confirmed_block.block_time = Some(bank.clock().unix_timestamp);
confirmed_block.block_time =
Some(bank.clock().unix_timestamp);
}
if confirmed_block.block_height.is_none() {
confirmed_block.block_height = Some(bank.block_height());
}
}
}
confirmed_block.configure(encoding, transaction_details, show_rewards)
}));
Ok(confirmed_block.configure(
encoding,
transaction_details,
show_rewards,
))
})
.transpose();
}
}
} else {
@ -1368,15 +1383,20 @@ impl JsonRpcRequestProcessor {
if self.config.enable_rpc_transaction_history {
let confirmed_bank = self.bank(Some(CommitmentConfig::confirmed()));
let transaction = if commitment.is_confirmed() {
let versioned_confirmed_tx = if commitment.is_confirmed() {
let highest_confirmed_slot = confirmed_bank.slot();
self.blockstore
.get_complete_transaction(signature, highest_confirmed_slot)
} else {
self.blockstore.get_rooted_transaction(signature)
};
match transaction.unwrap_or(None) {
Some(mut confirmed_transaction) => {
match versioned_confirmed_tx.unwrap_or(None) {
Some(versioned_confirmed_tx) => {
let mut confirmed_transaction = versioned_confirmed_tx
.into_legacy_confirmed_transaction()
.ok_or(RpcCustomError::UnsupportedTransactionVersion)?;
if commitment.is_confirmed()
&& confirmed_bank // should be redundant
.status_cache_ancestors()
@ -1402,11 +1422,18 @@ impl JsonRpcRequestProcessor {
}
None => {
if let Some(bigtable_ledger_storage) = &self.bigtable_ledger_storage {
return Ok(bigtable_ledger_storage
return bigtable_ledger_storage
.get_confirmed_transaction(&signature)
.await
.unwrap_or(None)
.map(|confirmed| confirmed.encode(encoding)));
.map(|versioned_confirmed_tx| {
let confirmed_tx = versioned_confirmed_tx
.into_legacy_confirmed_transaction()
.ok_or(RpcCustomError::UnsupportedTransactionVersion)?;
Ok(confirmed_tx.encode(encoding))
})
.transpose();
}
}
}

View File

@ -957,7 +957,20 @@ impl RpcSubscriptions {
if s > max_complete_transaction_status_slot.load(Ordering::SeqCst) {
break;
}
match blockstore.get_complete_block(s, false) {
let block_result = blockstore
.get_complete_block(s, false)
.map_err(|e| {
error!("get_complete_block error: {}", e);
RpcBlockUpdateError::BlockStoreError
})
.and_then(|versioned_block| {
versioned_block.into_legacy_block().ok_or(
RpcBlockUpdateError::UnsupportedTransactionVersion,
)
});
match block_result {
Ok(block) => {
if let Some(res) = filter_block_result_txs(block, s, params)
{
@ -975,17 +988,16 @@ impl RpcSubscriptions {
*w_last_unnotified_slot = s + 1;
}
}
Err(e) => {
Err(err) => {
// we don't advance `w_last_unnotified_slot` so that
// it'll retry on the next notification trigger
error!("get_complete_block error: {}", e);
notifier.notify(
Response {
context: RpcResponseContext { slot: s },
value: RpcBlockUpdate {
slot,
block: None,
err: Some(RpcBlockUpdateError::BlockStoreError),
err: Some(err),
},
},
subscription,
@ -1398,8 +1410,9 @@ pub(crate) mod tests {
let actual_resp = receiver.recv();
let actual_resp = serde_json::from_str::<serde_json::Value>(&actual_resp).unwrap();
let block = blockstore.get_complete_block(slot, false).unwrap();
let block = block.configure(params.encoding, params.transaction_details, false);
let versioned_block = blockstore.get_complete_block(slot, false).unwrap();
let legacy_block = versioned_block.into_legacy_block().unwrap();
let block = legacy_block.configure(params.encoding, params.transaction_details, false);
let expected_resp = RpcBlockUpdate {
slot,
block: Some(block),
@ -1497,14 +1510,16 @@ pub(crate) mod tests {
let actual_resp = serde_json::from_str::<serde_json::Value>(&actual_resp).unwrap();
// make sure it filtered out the other keypairs
let mut block = blockstore.get_complete_block(slot, false).unwrap();
block.transactions.retain(|tx| {
tx.transaction
let versioned_block = blockstore.get_complete_block(slot, false).unwrap();
let mut legacy_block = versioned_block.into_legacy_block().unwrap();
legacy_block.transactions.retain(|tx_with_meta| {
tx_with_meta
.transaction
.message
.account_keys
.contains(&keypair1.pubkey())
});
let block = block.configure(params.encoding, params.transaction_details, false);
let block = legacy_block.configure(params.encoding, params.transaction_details, false);
let expected_resp = RpcBlockUpdate {
slot,
block: Some(block),
@ -1594,8 +1609,9 @@ pub(crate) mod tests {
let actual_resp = receiver.recv();
let actual_resp = serde_json::from_str::<serde_json::Value>(&actual_resp).unwrap();
let block = blockstore.get_complete_block(slot, false).unwrap();
let block = block.configure(params.encoding, params.transaction_details, false);
let versioned_block = blockstore.get_complete_block(slot, false).unwrap();
let legacy_block = versioned_block.into_legacy_block().unwrap();
let block = legacy_block.configure(params.encoding, params.transaction_details, false);
let expected_resp = RpcBlockUpdate {
slot,
block: Some(block),

View File

@ -141,7 +141,7 @@ impl TransactionStatusService {
})
.collect(),
);
let loaded_addresses = transaction.get_loaded_addresses();
let transaction_status_meta = TransactionStatusMeta {
status,
fee,
@ -152,6 +152,7 @@ impl TransactionStatusService {
pre_token_balances,
post_token_balances,
rewards,
loaded_addresses,
};
if let Some(transaction_notifier) = transaction_notifier.as_ref() {

View File

@ -71,6 +71,24 @@ impl VersionedMessage {
}
}
pub fn total_account_keys_len(&self) -> usize {
match self {
Self::Legacy(message) => message.account_keys.len(),
Self::V0(message) => message.account_keys.len().saturating_add(
message
.address_table_lookups
.iter()
.map(|lookup| {
lookup
.writable_indexes
.len()
.saturating_add(lookup.readonly_indexes.len())
})
.sum(),
),
}
}
pub fn recent_blockhash(&self) -> &Hash {
match self {
Self::Legacy(message) => &message.recent_blockhash,
@ -85,6 +103,13 @@ impl VersionedMessage {
}
}
pub fn instructions(&self) -> &[CompiledInstruction] {
match self {
Self::Legacy(message) => &message.instructions,
Self::V0(message) => &message.instructions,
}
}
pub fn serialize(&self) -> Vec<u8> {
bincode::serialize(self).unwrap()
}

View File

@ -5,7 +5,7 @@ use {
pubkey::Pubkey,
sysvar,
},
std::{collections::HashSet, convert::TryFrom, ops::Deref},
std::{collections::HashSet, ops::Deref},
};
/// Combination of a version #0 message and its loaded addresses
@ -47,6 +47,30 @@ impl FromIterator<LoadedAddresses> for LoadedAddresses {
}
}
impl LoadedAddresses {
/// Checks if there are no writable or readonly addresses
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Combined length of loaded writable and readonly addresses
pub fn len(&self) -> usize {
self.writable.len().saturating_add(self.readonly.len())
}
/// Iterate over loaded addresses in the order that they are used
/// as account indexes
pub fn ordered_iter(&self) -> impl Iterator<Item = &Pubkey> {
self.writable.iter().chain(self.readonly.iter())
}
/// Iterate over loaded addresses in the order that they are used
/// as account indexes
pub fn into_ordered_iter(self) -> impl Iterator<Item = Pubkey> {
self.writable.into_iter().chain(self.readonly.into_iter())
}
}
impl LoadedMessage {
/// Returns an iterator of account key segments. The ordering of segments
/// affects how account indexes from compiled instructions are resolved and

View File

@ -48,6 +48,9 @@ pub enum SignerError {
#[error("{0}")]
UserCancel(String),
#[error("too many signers")]
TooManySigners,
}
/// The `Signer` trait declares operations that all digital signature providers

View File

@ -176,6 +176,14 @@ impl SanitizedTransaction {
account_locks
}
/// Return the list of addresses loaded from on-chain address lookup tables
pub fn get_loaded_addresses(&self) -> LoadedAddresses {
match &self.message {
SanitizedMessage::Legacy(_) => LoadedAddresses::default(),
SanitizedMessage::V0(message) => message.loaded_addresses.clone(),
}
}
/// If the transaction uses a durable nonce, return the pubkey of the nonce account
pub fn get_durable_nonce(&self, nonce_must_be_writable: bool) -> Option<&Pubkey> {
self.message.get_durable_nonce(nonce_must_be_writable)

View File

@ -9,6 +9,8 @@ use {
sanitize::{Sanitize, SanitizeError},
short_vec,
signature::Signature,
signer::SignerError,
signers::Signers,
transaction::{Result, Transaction, TransactionError},
},
serde::Serialize,
@ -57,6 +59,40 @@ impl From<Transaction> for VersionedTransaction {
}
impl VersionedTransaction {
/// Signs a versioned message and if successful, returns a signed
/// transaction.
pub fn try_new<T: Signers>(
message: VersionedMessage,
keypairs: &T,
) -> std::result::Result<Self, SignerError> {
let static_account_keys = message.static_account_keys();
if static_account_keys.len() < message.header().num_required_signatures as usize {
return Err(SignerError::InvalidInput("invalid message".to_string()));
}
let signer_keys = keypairs.pubkeys();
let expected_signer_keys =
&static_account_keys[0..message.header().num_required_signatures as usize];
match signer_keys.len().cmp(&expected_signer_keys.len()) {
Ordering::Greater => Err(SignerError::TooManySigners),
Ordering::Less => Err(SignerError::NotEnoughSigners),
Ordering::Equal => Ok(()),
}?;
if signer_keys != expected_signer_keys {
return Err(SignerError::KeypairPubkeyMismatch);
}
let message_data = message.serialize();
let signatures = keypairs.try_sign_message(&message_data)?;
Ok(Self {
signatures,
message,
})
}
/// Returns a legacy transaction if the transaction message is legacy.
pub fn into_legacy_transaction(self) -> Option<Transaction> {
match self.message {

View File

@ -790,10 +790,13 @@ mod tests {
super::*,
crate::StoredConfirmedBlock,
prost::Message,
solana_sdk::{hash::Hash, signature::Keypair, system_transaction},
solana_sdk::{
hash::Hash, message::v0::LoadedAddresses, signature::Keypair, system_transaction,
},
solana_storage_proto::convert::generated,
solana_transaction_status::{
ConfirmedBlock, TransactionStatusMeta, TransactionWithStatusMeta,
VersionedConfirmedBlock,
},
std::convert::TryInto,
};
@ -815,6 +818,7 @@ mod tests {
pre_token_balances: Some(vec![]),
post_token_balances: Some(vec![]),
rewards: Some(vec![]),
loaded_addresses: LoadedAddresses::default(),
}),
};
let block = ConfirmedBlock {
@ -845,8 +849,9 @@ mod tests {
"".to_string(),
)
.unwrap();
let expected_block: VersionedConfirmedBlock = block.into();
if let CellData::Protobuf(protobuf_block) = deserialized {
assert_eq!(block, protobuf_block.try_into().unwrap());
assert_eq!(expected_block, protobuf_block.try_into().unwrap());
} else {
panic!("deserialization should produce CellData::Protobuf");
}
@ -861,7 +866,7 @@ mod tests {
)
.unwrap();
if let CellData::Bincode(bincode_block) = deserialized {
let mut block = block;
let mut block = expected_block;
if let Some(meta) = &mut block.transactions[0].meta {
meta.inner_instructions = None; // Legacy bincode implementation does not support inner_instructions
meta.log_messages = None; // Legacy bincode implementation does not support log_messages

View File

@ -6,17 +6,18 @@ use {
solana_sdk::{
clock::{Slot, UnixTimestamp},
deserialize_utils::default_on_eof,
message::v0::LoadedAddresses,
pubkey::Pubkey,
signature::Signature,
sysvar::is_sysvar_id,
transaction::{Transaction, TransactionError},
transaction::{TransactionError, VersionedTransaction},
},
solana_storage_proto::convert::{generated, tx_by_addr},
solana_transaction_status::{
extract_and_fmt_memos, ConfirmedBlock, ConfirmedTransactionStatusWithSignature,
ConfirmedTransactionWithStatusMeta, Reward, TransactionByAddrInfo,
TransactionConfirmationStatus, TransactionStatus, TransactionStatusMeta,
TransactionWithStatusMeta,
extract_and_fmt_memos, ConfirmedBlock, ConfirmedTransactionStatusWithSignature, Reward,
TransactionByAddrInfo, TransactionConfirmationStatus, TransactionStatus,
TransactionStatusMeta, TransactionWithStatusMeta, VersionedConfirmedBlock,
VersionedConfirmedTransactionWithStatusMeta, VersionedTransactionWithStatusMeta,
},
std::{
collections::{HashMap, HashSet},
@ -138,7 +139,7 @@ impl From<ConfirmedBlock> for StoredConfirmedBlock {
}
}
impl From<StoredConfirmedBlock> for ConfirmedBlock {
impl From<StoredConfirmedBlock> for VersionedConfirmedBlock {
fn from(confirmed_block: StoredConfirmedBlock) -> Self {
let StoredConfirmedBlock {
previous_blockhash,
@ -164,20 +165,20 @@ impl From<StoredConfirmedBlock> for ConfirmedBlock {
#[derive(Serialize, Deserialize)]
struct StoredConfirmedBlockTransaction {
transaction: Transaction,
transaction: VersionedTransaction,
meta: Option<StoredConfirmedBlockTransactionStatusMeta>,
}
impl From<TransactionWithStatusMeta> for StoredConfirmedBlockTransaction {
fn from(value: TransactionWithStatusMeta) -> Self {
Self {
transaction: value.transaction,
transaction: value.transaction.into(),
meta: value.meta.map(|meta| meta.into()),
}
}
}
impl From<StoredConfirmedBlockTransaction> for TransactionWithStatusMeta {
impl From<StoredConfirmedBlockTransaction> for VersionedTransactionWithStatusMeta {
fn from(value: StoredConfirmedBlockTransaction) -> Self {
Self {
transaction: value.transaction,
@ -216,6 +217,7 @@ impl From<StoredConfirmedBlockTransactionStatusMeta> for TransactionStatusMeta {
pre_token_balances: None,
post_token_balances: None,
rewards: None,
loaded_addresses: LoadedAddresses::default(),
}
}
}
@ -392,7 +394,7 @@ impl LedgerStorage {
}
/// Fetch the confirmed block from the desired slot
pub async fn get_confirmed_block(&self, slot: Slot) -> Result<ConfirmedBlock> {
pub async fn get_confirmed_block(&self, slot: Slot) -> Result<VersionedConfirmedBlock> {
debug!(
"LedgerStorage::get_confirmed_block request received: {:?}",
slot
@ -438,7 +440,7 @@ impl LedgerStorage {
pub async fn get_confirmed_transaction(
&self,
signature: &Signature,
) -> Result<Option<ConfirmedTransactionWithStatusMeta>> {
) -> Result<Option<VersionedConfirmedTransactionWithStatusMeta>> {
debug!(
"LedgerStorage::get_confirmed_transaction request received: {:?}",
signature
@ -471,9 +473,9 @@ impl LedgerStorage {
);
Ok(None)
} else {
Ok(Some(ConfirmedTransactionWithStatusMeta {
Ok(Some(VersionedConfirmedTransactionWithStatusMeta {
slot,
transaction: bucket_block_transaction,
tx_with_meta: bucket_block_transaction,
block_time: block.block_time,
}))
}
@ -627,7 +629,7 @@ impl LedgerStorage {
pub async fn upload_confirmed_block(
&self,
slot: Slot,
confirmed_block: ConfirmedBlock,
confirmed_block: VersionedConfirmedBlock,
) -> Result<()> {
let mut bytes_written = 0;
@ -635,13 +637,13 @@ impl LedgerStorage {
let mut tx_cells = vec![];
for (index, transaction_with_meta) in confirmed_block.transactions.iter().enumerate() {
let TransactionWithStatusMeta { meta, transaction } = transaction_with_meta;
let VersionedTransactionWithStatusMeta { meta, transaction } = transaction_with_meta;
let err = meta.as_ref().and_then(|meta| meta.status.clone().err());
let index = index as u32;
let signature = transaction.signatures[0];
let memo = extract_and_fmt_memos(&transaction.message);
let memo = extract_and_fmt_memos(transaction_with_meta);
for address in &transaction.message.account_keys {
for address in transaction_with_meta.account_keys_iter() {
if !is_sysvar_id(address) {
by_addr
.entry(address)
@ -723,12 +725,12 @@ impl LedgerStorage {
let mut expected_tx_infos: HashMap<String, UploadedTransaction> = HashMap::new();
let confirmed_block = self.get_confirmed_block(slot).await?;
for (index, transaction_with_meta) in confirmed_block.transactions.iter().enumerate() {
let TransactionWithStatusMeta { meta, transaction } = transaction_with_meta;
let VersionedTransactionWithStatusMeta { transaction, meta } = transaction_with_meta;
let signature = transaction.signatures[0];
let index = index as u32;
let err = meta.as_ref().and_then(|meta| meta.status.clone().err());
for address in &transaction.message.account_keys {
for address in transaction_with_meta.account_keys_iter() {
if !is_sysvar_id(address) {
addresses.insert(address);
}

View File

@ -27,6 +27,8 @@ message Message {
repeated bytes account_keys = 2;
bytes recent_blockhash = 3;
repeated CompiledInstruction instructions = 4;
bool versioned = 5;
repeated MessageAddressTableLookup address_table_lookups = 6;
}
message MessageHeader {
@ -35,6 +37,12 @@ message MessageHeader {
uint32 num_readonly_unsigned_accounts = 3;
}
message MessageAddressTableLookup {
bytes account_key = 1;
bytes writable_indexes = 2;
bytes readonly_indexes = 3;
}
message TransactionStatusMeta {
TransactionError err = 1;
uint64 fee = 2;
@ -47,6 +55,8 @@ message TransactionStatusMeta {
repeated TokenBalance pre_token_balances = 7;
repeated TokenBalance post_token_balances = 8;
repeated Reward rewards = 9;
repeated bytes loaded_writable_addresses = 12;
repeated bytes loaded_readonly_addresses = 13;
}
message TransactionError {

View File

@ -4,14 +4,19 @@ use {
solana_sdk::{
hash::Hash,
instruction::{CompiledInstruction, InstructionError},
message::{Message, MessageHeader},
message::{
legacy::Message as LegacyMessage,
v0::{self, LoadedAddresses, MessageAddressTableLookup},
MessageHeader, VersionedMessage,
},
pubkey::Pubkey,
signature::Signature,
transaction::{Transaction, TransactionError},
transaction::{Transaction, TransactionError, VersionedTransaction},
},
solana_transaction_status::{
ConfirmedBlock, InnerInstructions, Reward, RewardType, TransactionByAddrInfo,
TransactionStatusMeta, TransactionTokenBalance, TransactionWithStatusMeta,
VersionedConfirmedBlock, VersionedTransactionWithStatusMeta,
},
std::{
convert::{TryFrom, TryInto},
@ -111,6 +116,30 @@ impl From<generated::Reward> for Reward {
}
}
impl From<VersionedConfirmedBlock> for generated::ConfirmedBlock {
fn from(confirmed_block: VersionedConfirmedBlock) -> Self {
let VersionedConfirmedBlock {
previous_blockhash,
blockhash,
parent_slot,
transactions,
rewards,
block_time,
block_height,
} = confirmed_block;
Self {
previous_blockhash,
blockhash,
parent_slot,
transactions: transactions.into_iter().map(|tx| tx.into()).collect(),
rewards: rewards.into_iter().map(|r| r.into()).collect(),
block_time: block_time.map(|timestamp| generated::UnixTimestamp { timestamp }),
block_height: block_height.map(|block_height| generated::BlockHeight { block_height }),
}
}
}
impl From<ConfirmedBlock> for generated::ConfirmedBlock {
fn from(confirmed_block: ConfirmedBlock) -> Self {
let ConfirmedBlock {
@ -135,7 +164,7 @@ impl From<ConfirmedBlock> for generated::ConfirmedBlock {
}
}
impl TryFrom<generated::ConfirmedBlock> for ConfirmedBlock {
impl TryFrom<generated::ConfirmedBlock> for VersionedConfirmedBlock {
type Error = bincode::Error;
fn try_from(
confirmed_block: generated::ConfirmedBlock,
@ -157,7 +186,7 @@ impl TryFrom<generated::ConfirmedBlock> for ConfirmedBlock {
transactions: transactions
.into_iter()
.map(|tx| tx.try_into())
.collect::<std::result::Result<Vec<TransactionWithStatusMeta>, Self::Error>>()?,
.collect::<std::result::Result<Vec<_>, Self::Error>>()?,
rewards: rewards.into_iter().map(|r| r.into()).collect(),
block_time: block_time.map(|generated::UnixTimestamp { timestamp }| timestamp),
block_height: block_height.map(|generated::BlockHeight { block_height }| block_height),
@ -175,7 +204,17 @@ impl From<TransactionWithStatusMeta> for generated::ConfirmedTransaction {
}
}
impl TryFrom<generated::ConfirmedTransaction> for TransactionWithStatusMeta {
impl From<VersionedTransactionWithStatusMeta> for generated::ConfirmedTransaction {
fn from(value: VersionedTransactionWithStatusMeta) -> Self {
let meta = value.meta.map(|meta| meta.into());
Self {
transaction: Some(value.transaction.into()),
meta,
}
}
}
impl TryFrom<generated::ConfirmedTransaction> for VersionedTransactionWithStatusMeta {
type Error = bincode::Error;
fn try_from(value: generated::ConfirmedTransaction) -> std::result::Result<Self, Self::Error> {
let meta = value.meta.map(|meta| meta.try_into()).transpose()?;
@ -199,7 +238,20 @@ impl From<Transaction> for generated::Transaction {
}
}
impl From<generated::Transaction> for Transaction {
impl From<VersionedTransaction> for generated::Transaction {
fn from(value: VersionedTransaction) -> Self {
Self {
signatures: value
.signatures
.into_iter()
.map(|signature| <Signature as AsRef<[u8]>>::as_ref(&signature).into())
.collect(),
message: Some(value.message.into()),
}
}
}
impl From<generated::Transaction> for VersionedTransaction {
fn from(value: generated::Transaction) -> Self {
Self {
signatures: value
@ -212,32 +264,86 @@ impl From<generated::Transaction> for Transaction {
}
}
impl From<Message> for generated::Message {
fn from(value: Message) -> Self {
impl From<LegacyMessage> for generated::Message {
fn from(message: LegacyMessage) -> Self {
Self {
header: Some(value.header.into()),
account_keys: value
header: Some(message.header.into()),
account_keys: message
.account_keys
.into_iter()
.map(|key| <Pubkey as AsRef<[u8]>>::as_ref(&key).into())
.iter()
.map(|key| <Pubkey as AsRef<[u8]>>::as_ref(key).into())
.collect(),
recent_blockhash: value.recent_blockhash.to_bytes().into(),
instructions: value.instructions.into_iter().map(|ix| ix.into()).collect(),
recent_blockhash: message.recent_blockhash.to_bytes().into(),
instructions: message
.instructions
.into_iter()
.map(|ix| ix.into())
.collect(),
versioned: false,
address_table_lookups: vec![],
}
}
}
impl From<generated::Message> for Message {
impl From<VersionedMessage> for generated::Message {
fn from(message: VersionedMessage) -> Self {
match message {
VersionedMessage::Legacy(message) => Self::from(message),
VersionedMessage::V0(message) => Self {
header: Some(message.header.into()),
account_keys: message
.account_keys
.iter()
.map(|key| <Pubkey as AsRef<[u8]>>::as_ref(key).into())
.collect(),
recent_blockhash: message.recent_blockhash.to_bytes().into(),
instructions: message
.instructions
.into_iter()
.map(|ix| ix.into())
.collect(),
versioned: true,
address_table_lookups: message
.address_table_lookups
.into_iter()
.map(|lookup| lookup.into())
.collect(),
},
}
}
}
impl From<generated::Message> for VersionedMessage {
fn from(value: generated::Message) -> Self {
Self {
header: value.header.expect("header is required").into(),
account_keys: value
let header = value.header.expect("header is required").into();
let account_keys = value
.account_keys
.into_iter()
.map(|key| Pubkey::new(&key))
.collect(),
recent_blockhash: Hash::new(&value.recent_blockhash),
instructions: value.instructions.into_iter().map(|ix| ix.into()).collect(),
.collect();
let recent_blockhash = Hash::new(&value.recent_blockhash);
let instructions = value.instructions.into_iter().map(|ix| ix.into()).collect();
let address_table_lookups = value
.address_table_lookups
.into_iter()
.map(|lookup| lookup.into())
.collect();
if !value.versioned {
Self::Legacy(LegacyMessage {
header,
account_keys,
recent_blockhash,
instructions,
})
} else {
Self::V0(v0::Message {
header,
account_keys,
recent_blockhash,
instructions,
address_table_lookups,
})
}
}
}
@ -274,6 +380,7 @@ impl From<TransactionStatusMeta> for generated::TransactionStatusMeta {
pre_token_balances,
post_token_balances,
rewards,
loaded_addresses,
} = value;
let err = match status {
Ok(()) => None,
@ -304,6 +411,16 @@ impl From<TransactionStatusMeta> for generated::TransactionStatusMeta {
.into_iter()
.map(|reward| reward.into())
.collect();
let loaded_writable_addresses = loaded_addresses
.writable
.into_iter()
.map(|key| <Pubkey as AsRef<[u8]>>::as_ref(&key).into())
.collect();
let loaded_readonly_addresses = loaded_addresses
.readonly
.into_iter()
.map(|key| <Pubkey as AsRef<[u8]>>::as_ref(&key).into())
.collect();
Self {
err,
@ -317,6 +434,8 @@ impl From<TransactionStatusMeta> for generated::TransactionStatusMeta {
pre_token_balances,
post_token_balances,
rewards,
loaded_writable_addresses,
loaded_readonly_addresses,
}
}
}
@ -344,6 +463,8 @@ impl TryFrom<generated::TransactionStatusMeta> for TransactionStatusMeta {
pre_token_balances,
post_token_balances,
rewards,
loaded_writable_addresses,
loaded_readonly_addresses,
} = value;
let status = match &err {
None => Ok(()),
@ -377,6 +498,16 @@ impl TryFrom<generated::TransactionStatusMeta> for TransactionStatusMeta {
.collect(),
);
let rewards = Some(rewards.into_iter().map(|reward| reward.into()).collect());
let loaded_addresses = LoadedAddresses {
writable: loaded_writable_addresses
.into_iter()
.map(|key| Pubkey::new(&key))
.collect(),
readonly: loaded_readonly_addresses
.into_iter()
.map(|key| Pubkey::new(&key))
.collect(),
};
Ok(Self {
status,
fee,
@ -387,6 +518,7 @@ impl TryFrom<generated::TransactionStatusMeta> for TransactionStatusMeta {
pre_token_balances,
post_token_balances,
rewards,
loaded_addresses,
})
}
}
@ -453,6 +585,26 @@ impl From<generated::TokenBalance> for TransactionTokenBalance {
}
}
impl From<MessageAddressTableLookup> for generated::MessageAddressTableLookup {
fn from(lookup: MessageAddressTableLookup) -> Self {
Self {
account_key: <Pubkey as AsRef<[u8]>>::as_ref(&lookup.account_key).into(),
writable_indexes: lookup.writable_indexes,
readonly_indexes: lookup.readonly_indexes,
}
}
}
impl From<generated::MessageAddressTableLookup> for MessageAddressTableLookup {
fn from(value: generated::MessageAddressTableLookup) -> Self {
Self {
account_key: Pubkey::new(&value.account_key),
writable_indexes: value.writable_indexes,
readonly_indexes: value.readonly_indexes,
}
}
}
impl From<CompiledInstruction> for generated::CompiledInstruction {
fn from(value: CompiledInstruction) -> Self {
Self {

View File

@ -4,7 +4,9 @@ use {
parse_token::{real_number_string_trimmed, UiTokenAmount},
StringAmount,
},
solana_sdk::{deserialize_utils::default_on_eof, transaction::Result},
solana_sdk::{
deserialize_utils::default_on_eof, message::v0::LoadedAddresses, transaction::Result,
},
solana_transaction_status::{
InnerInstructions, Reward, RewardType, TransactionStatusMeta, TransactionTokenBalance,
},
@ -193,12 +195,14 @@ impl From<StoredTransactionStatusMeta> for TransactionStatusMeta {
.map(|balances| balances.into_iter().map(|balance| balance.into()).collect()),
rewards: rewards
.map(|rewards| rewards.into_iter().map(|reward| reward.into()).collect()),
loaded_addresses: LoadedAddresses::default(),
}
}
}
impl From<TransactionStatusMeta> for StoredTransactionStatusMeta {
fn from(value: TransactionStatusMeta) -> Self {
impl TryFrom<TransactionStatusMeta> for StoredTransactionStatusMeta {
type Error = bincode::Error;
fn try_from(value: TransactionStatusMeta) -> std::result::Result<Self, Self::Error> {
let TransactionStatusMeta {
status,
fee,
@ -209,8 +213,18 @@ impl From<TransactionStatusMeta> for StoredTransactionStatusMeta {
pre_token_balances,
post_token_balances,
rewards,
loaded_addresses,
} = value;
Self {
if !loaded_addresses.is_empty() {
// Deprecated bincode serialized status metadata doesn't support
// loaded addresses.
return Err(
bincode::ErrorKind::Custom("Bincode serialization is deprecated".into()).into(),
);
}
Ok(Self {
status,
fee,
pre_balances,
@ -223,6 +237,6 @@ impl From<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()),
}
})
}
}

View File

@ -1,5 +1,5 @@
use {
crate::parse_instruction::parse_memo_data,
crate::{parse_instruction::parse_memo_data, VersionedTransactionWithStatusMeta},
solana_sdk::{
instruction::CompiledInstruction,
message::{Message, SanitizedMessage},
@ -55,6 +55,15 @@ impl ExtractMemos for SanitizedMessage {
}
}
impl ExtractMemos for VersionedTransactionWithStatusMeta {
fn extract_memos(&self) -> Vec<String> {
extract_memos_inner(
self.account_keys_iter(),
self.transaction.message.instructions(),
)
}
}
enum KeyType<'a> {
MemoProgram,
OtherProgram,

View File

@ -26,11 +26,11 @@ use {
clock::{Slot, UnixTimestamp},
commitment_config::CommitmentConfig,
instruction::CompiledInstruction,
message::{Message, MessageHeader},
message::{v0::LoadedAddresses, Message, MessageHeader},
pubkey::Pubkey,
sanitize::Sanitize,
signature::Signature,
transaction::{Result, Transaction, TransactionError},
transaction::{Result, Transaction, TransactionError, VersionedTransaction},
},
std::fmt,
};
@ -82,13 +82,13 @@ pub enum UiInstruction {
}
impl UiInstruction {
fn parse(instruction: &CompiledInstruction, message: &Message) -> Self {
let program_id = instruction.program_id(&message.account_keys);
if let Ok(parsed_instruction) = parse(program_id, instruction, &message.account_keys) {
fn parse(instruction: &CompiledInstruction, account_keys: &[Pubkey]) -> Self {
let program_id = instruction.program_id(account_keys);
if let Ok(parsed_instruction) = parse(program_id, instruction, account_keys) {
UiInstruction::Parsed(UiParsedInstruction::Parsed(parsed_instruction))
} else {
UiInstruction::Parsed(UiParsedInstruction::PartiallyDecoded(
UiPartiallyDecodedInstruction::from(instruction, &message.account_keys),
UiPartiallyDecodedInstruction::from(instruction, account_keys),
))
}
}
@ -167,7 +167,7 @@ impl UiInnerInstructions {
instructions: inner_instructions
.instructions
.iter()
.map(|ix| UiInstruction::parse(ix, message))
.map(|ix| UiInstruction::parse(ix, &message.account_keys))
.collect(),
}
}
@ -230,6 +230,7 @@ pub struct TransactionStatusMeta {
pub pre_token_balances: Option<Vec<TransactionTokenBalance>>,
pub post_token_balances: Option<Vec<TransactionTokenBalance>>,
pub rewards: Option<Rewards>,
pub loaded_addresses: LoadedAddresses,
}
impl Default for TransactionStatusMeta {
@ -244,6 +245,7 @@ impl Default for TransactionStatusMeta {
pre_token_balances: None,
post_token_balances: None,
rewards: None,
loaded_addresses: LoadedAddresses::default(),
}
}
}
@ -397,6 +399,51 @@ pub struct ConfirmedBlock {
pub block_height: Option<u64>,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct VersionedConfirmedBlock {
pub previous_blockhash: String,
pub blockhash: String,
pub parent_slot: Slot,
pub transactions: Vec<VersionedTransactionWithStatusMeta>,
pub rewards: Rewards,
pub block_time: Option<UnixTimestamp>,
pub block_height: Option<u64>,
}
impl VersionedConfirmedBlock {
/// Downgrades a versioned block into a legacy block type
/// if it only contains legacy transactions
pub fn into_legacy_block(self) -> Option<ConfirmedBlock> {
Some(ConfirmedBlock {
previous_blockhash: self.previous_blockhash,
blockhash: self.blockhash,
parent_slot: self.parent_slot,
transactions: self
.transactions
.into_iter()
.map(|tx_with_meta| tx_with_meta.into_legacy_transaction_with_meta())
.collect::<Option<Vec<_>>>()?,
rewards: self.rewards,
block_time: self.block_time,
block_height: self.block_height,
})
}
}
impl From<ConfirmedBlock> for VersionedConfirmedBlock {
fn from(block: ConfirmedBlock) -> Self {
VersionedConfirmedBlock {
previous_blockhash: block.previous_blockhash,
blockhash: block.blockhash,
parent_slot: block.parent_slot,
transactions: block.transactions.into_iter().map(|tx| tx.into()).collect(),
rewards: block.rewards,
block_time: block.block_time,
block_height: block.block_height,
}
}
}
impl Encodable for ConfirmedBlock {
type Encoded = EncodedConfirmedBlock;
fn encode(self, encoding: UiTransactionEncoding) -> Self::Encoded {
@ -428,7 +475,7 @@ impl ConfirmedBlock {
Some(
self.transactions
.into_iter()
.map(|tx| tx.encode(encoding))
.map(|tx_with_meta| tx_with_meta.encode(encoding))
.collect(),
),
None,
@ -518,6 +565,41 @@ impl From<EncodedConfirmedBlock> for UiConfirmedBlock {
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct VersionedTransactionWithStatusMeta {
pub transaction: VersionedTransaction,
pub meta: Option<TransactionStatusMeta>,
}
impl VersionedTransactionWithStatusMeta {
pub fn account_keys_iter(&self) -> impl Iterator<Item = &Pubkey> {
let static_keys_iter = self.transaction.message.static_account_keys().iter();
let dynamic_keys_iter = self
.meta
.iter()
.map(|meta| meta.loaded_addresses.ordered_iter())
.flatten();
static_keys_iter.chain(dynamic_keys_iter)
}
pub fn into_legacy_transaction_with_meta(self) -> Option<TransactionWithStatusMeta> {
Some(TransactionWithStatusMeta {
transaction: self.transaction.into_legacy_transaction()?,
meta: self.meta,
})
}
}
impl From<TransactionWithStatusMeta> for VersionedTransactionWithStatusMeta {
fn from(tx_with_meta: TransactionWithStatusMeta) -> Self {
Self {
transaction: tx_with_meta.transaction.into(),
meta: tx_with_meta.meta,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct TransactionWithStatusMeta {
pub transaction: Transaction,
@ -531,7 +613,7 @@ impl Encodable for TransactionWithStatusMeta {
transaction: self.transaction.encode(encoding),
meta: self.meta.map(|meta| match encoding {
UiTransactionEncoding::JsonParsed => {
UiTransactionStatusMeta::parse(meta, self.transaction.message())
UiTransactionStatusMeta::parse(meta, &self.transaction.message)
}
_ => UiTransactionStatusMeta::from(meta),
}),
@ -545,6 +627,7 @@ pub struct EncodedTransactionWithStatusMeta {
pub transaction: EncodedTransaction,
pub meta: Option<UiTransactionStatusMeta>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ConfirmedTransactionWithStatusMeta {
pub slot: Slot,
@ -563,6 +646,28 @@ impl Encodable for ConfirmedTransactionWithStatusMeta {
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct VersionedConfirmedTransactionWithStatusMeta {
pub slot: Slot,
pub tx_with_meta: VersionedTransactionWithStatusMeta,
pub block_time: Option<UnixTimestamp>,
}
impl VersionedConfirmedTransactionWithStatusMeta {
/// Downgrades a versioned confirmed transaction into a legacy
/// confirmed transaction if it contains a legacy transaction.
pub fn into_legacy_confirmed_transaction(self) -> Option<ConfirmedTransactionWithStatusMeta> {
Some(ConfirmedTransactionWithStatusMeta {
transaction: TransactionWithStatusMeta {
transaction: self.tx_with_meta.transaction.into_legacy_transaction()?,
meta: self.tx_with_meta.meta,
},
block_time: self.block_time,
slot: self.slot,
})
}
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EncodedConfirmedTransactionWithStatusMeta {
@ -655,7 +760,7 @@ impl Encodable for &Message {
instructions: self
.instructions
.iter()
.map(|instruction| UiInstruction::parse(instruction, self))
.map(|instruction| UiInstruction::parse(instruction, &self.account_keys))
.collect(),
})
} else {