Add RPC support for versioned transactions (#22530)
* Add RPC support for versioned transactions * fix doc tests * Add rpc test for versioned txs * Switch to preflight bank
This commit is contained in:
@@ -18,7 +18,7 @@ pub mod token_balances;
|
||||
pub use {crate::extract_memos::extract_and_fmt_memos, solana_runtime::bank::RewardType};
|
||||
use {
|
||||
crate::{
|
||||
parse_accounts::{parse_accounts, ParsedAccount},
|
||||
parse_accounts::{parse_accounts, parse_static_accounts, ParsedAccount},
|
||||
parse_instruction::{parse, ParsedInstruction},
|
||||
},
|
||||
solana_account_decoder::parse_token::UiTokenAmount,
|
||||
@@ -26,18 +26,48 @@ use {
|
||||
clock::{Slot, UnixTimestamp},
|
||||
commitment_config::CommitmentConfig,
|
||||
instruction::CompiledInstruction,
|
||||
message::{v0::LoadedAddresses, AccountKeys, Message, MessageHeader},
|
||||
message::{
|
||||
v0::{self, LoadedAddresses, LoadedMessage, MessageAddressTableLookup},
|
||||
AccountKeys, Message, MessageHeader, VersionedMessage,
|
||||
},
|
||||
pubkey::Pubkey,
|
||||
sanitize::Sanitize,
|
||||
signature::Signature,
|
||||
transaction::{Result, Transaction, TransactionError, VersionedTransaction},
|
||||
transaction::{
|
||||
Result as TransactionResult, Transaction, TransactionError, TransactionVersion,
|
||||
VersionedTransaction,
|
||||
},
|
||||
},
|
||||
std::fmt,
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
pub struct BlockEncodingOptions {
|
||||
pub transaction_details: TransactionDetails,
|
||||
pub show_rewards: bool,
|
||||
pub max_supported_transaction_version: Option<u8>,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug, PartialEq, Eq, Clone)]
|
||||
pub enum EncodeError {
|
||||
#[error("Encoding does not support transaction version {0}")]
|
||||
UnsupportedTransactionVersion(u8),
|
||||
}
|
||||
|
||||
/// Represents types that can be encoded into one of several encoding formats
|
||||
pub trait Encodable {
|
||||
type Encoded;
|
||||
fn encode(self, encoding: UiTransactionEncoding) -> Self::Encoded;
|
||||
fn encode(&self, encoding: UiTransactionEncoding) -> Self::Encoded;
|
||||
}
|
||||
|
||||
/// Represents types that can be encoded into one of several encoding formats
|
||||
pub trait EncodableWithMeta {
|
||||
type Encoded;
|
||||
fn encode_with_meta(
|
||||
&self,
|
||||
encoding: UiTransactionEncoding,
|
||||
meta: &TransactionStatusMeta,
|
||||
) -> Self::Encoded;
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
@@ -160,14 +190,13 @@ pub struct UiInnerInstructions {
|
||||
}
|
||||
|
||||
impl UiInnerInstructions {
|
||||
fn parse(inner_instructions: InnerInstructions, message: &Message) -> Self {
|
||||
let account_keys = AccountKeys::new(&message.account_keys, None);
|
||||
fn parse(inner_instructions: InnerInstructions, account_keys: &AccountKeys) -> Self {
|
||||
Self {
|
||||
index: inner_instructions.index,
|
||||
instructions: inner_instructions
|
||||
.instructions
|
||||
.iter()
|
||||
.map(|ix| UiInstruction::parse(ix, &account_keys))
|
||||
.map(|ix| UiInstruction::parse(ix, account_keys))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
@@ -221,7 +250,7 @@ impl From<TransactionTokenBalance> for UiTransactionTokenBalance {
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct TransactionStatusMeta {
|
||||
pub status: Result<()>,
|
||||
pub status: TransactionResult<()>,
|
||||
pub fee: u64,
|
||||
pub pre_balances: Vec<u64>,
|
||||
pub post_balances: Vec<u64>,
|
||||
@@ -255,7 +284,7 @@ impl Default for TransactionStatusMeta {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiTransactionStatusMeta {
|
||||
pub err: Option<TransactionError>,
|
||||
pub status: Result<()>, // This field is deprecated. See https://github.com/solana-labs/solana/issues/9302
|
||||
pub status: TransactionResult<()>, // This field is deprecated. See https://github.com/solana-labs/solana/issues/9302
|
||||
pub fee: u64,
|
||||
pub pre_balances: Vec<u64>,
|
||||
pub post_balances: Vec<u64>,
|
||||
@@ -264,10 +293,38 @@ pub struct UiTransactionStatusMeta {
|
||||
pub pre_token_balances: Option<Vec<UiTransactionTokenBalance>>,
|
||||
pub post_token_balances: Option<Vec<UiTransactionTokenBalance>>,
|
||||
pub rewards: Option<Rewards>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub loaded_addresses: Option<UiLoadedAddresses>,
|
||||
}
|
||||
|
||||
/// A duplicate representation of LoadedAddresses
|
||||
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiLoadedAddresses {
|
||||
pub writable: Vec<String>,
|
||||
pub readonly: Vec<String>,
|
||||
}
|
||||
|
||||
impl From<&LoadedAddresses> for UiLoadedAddresses {
|
||||
fn from(loaded_addresses: &LoadedAddresses) -> Self {
|
||||
Self {
|
||||
writable: loaded_addresses
|
||||
.writable
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect(),
|
||||
readonly: loaded_addresses
|
||||
.readonly
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UiTransactionStatusMeta {
|
||||
fn parse(meta: TransactionStatusMeta, message: &Message) -> Self {
|
||||
fn parse(meta: TransactionStatusMeta, static_keys: &[Pubkey]) -> Self {
|
||||
let account_keys = AccountKeys::new(static_keys, Some(&meta.loaded_addresses));
|
||||
Self {
|
||||
err: meta.status.clone().err(),
|
||||
status: meta.status,
|
||||
@@ -276,7 +333,7 @@ impl UiTransactionStatusMeta {
|
||||
post_balances: meta.post_balances,
|
||||
inner_instructions: meta.inner_instructions.map(|ixs| {
|
||||
ixs.into_iter()
|
||||
.map(|ix| UiInnerInstructions::parse(ix, message))
|
||||
.map(|ix| UiInnerInstructions::parse(ix, &account_keys))
|
||||
.collect()
|
||||
}),
|
||||
log_messages: meta.log_messages,
|
||||
@@ -287,6 +344,7 @@ impl UiTransactionStatusMeta {
|
||||
.post_token_balances
|
||||
.map(|balance| balance.into_iter().map(Into::into).collect()),
|
||||
rewards: meta.rewards,
|
||||
loaded_addresses: Some(UiLoadedAddresses::from(&meta.loaded_addresses)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -310,6 +368,7 @@ impl From<TransactionStatusMeta> for UiTransactionStatusMeta {
|
||||
.post_token_balances
|
||||
.map(|balance| balance.into_iter().map(Into::into).collect()),
|
||||
rewards: meta.rewards,
|
||||
loaded_addresses: Some(UiLoadedAddresses::from(&meta.loaded_addresses)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -326,8 +385,8 @@ pub enum TransactionConfirmationStatus {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TransactionStatus {
|
||||
pub slot: Slot,
|
||||
pub confirmations: Option<usize>, // None = rooted
|
||||
pub status: Result<()>, // legacy field
|
||||
pub confirmations: Option<usize>, // None = rooted
|
||||
pub status: TransactionResult<()>, // legacy field
|
||||
pub err: Option<TransactionError>,
|
||||
pub confirmation_status: Option<TransactionConfirmationStatus>,
|
||||
}
|
||||
@@ -462,39 +521,21 @@ impl From<VersionedConfirmedBlock> for ConfirmedBlock {
|
||||
}
|
||||
}
|
||||
|
||||
impl Encodable for LegacyConfirmedBlock {
|
||||
type Encoded = EncodedConfirmedBlock;
|
||||
fn encode(self, encoding: UiTransactionEncoding) -> Self::Encoded {
|
||||
Self::Encoded {
|
||||
previous_blockhash: self.previous_blockhash,
|
||||
blockhash: self.blockhash,
|
||||
parent_slot: self.parent_slot,
|
||||
transactions: self
|
||||
.transactions
|
||||
.into_iter()
|
||||
.map(|tx| tx.encode(encoding))
|
||||
.collect(),
|
||||
rewards: self.rewards,
|
||||
block_time: self.block_time,
|
||||
block_height: self.block_height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LegacyConfirmedBlock {
|
||||
pub fn configure(
|
||||
impl ConfirmedBlock {
|
||||
pub fn encode_with_options(
|
||||
self,
|
||||
encoding: UiTransactionEncoding,
|
||||
transaction_details: TransactionDetails,
|
||||
show_rewards: bool,
|
||||
) -> UiConfirmedBlock {
|
||||
let (transactions, signatures) = match transaction_details {
|
||||
options: BlockEncodingOptions,
|
||||
) -> Result<UiConfirmedBlock, EncodeError> {
|
||||
let (transactions, signatures) = match options.transaction_details {
|
||||
TransactionDetails::Full => (
|
||||
Some(
|
||||
self.transactions
|
||||
.into_iter()
|
||||
.map(|tx_with_meta| tx_with_meta.encode(encoding))
|
||||
.collect(),
|
||||
.map(|tx_with_meta| {
|
||||
tx_with_meta.encode(encoding, options.max_supported_transaction_version)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
),
|
||||
None,
|
||||
),
|
||||
@@ -503,26 +544,26 @@ impl LegacyConfirmedBlock {
|
||||
Some(
|
||||
self.transactions
|
||||
.into_iter()
|
||||
.map(|tx| tx.transaction.signatures[0].to_string())
|
||||
.map(|tx_with_meta| tx_with_meta.transaction_signature().to_string())
|
||||
.collect(),
|
||||
),
|
||||
),
|
||||
TransactionDetails::None => (None, None),
|
||||
};
|
||||
UiConfirmedBlock {
|
||||
Ok(UiConfirmedBlock {
|
||||
previous_blockhash: self.previous_blockhash,
|
||||
blockhash: self.blockhash,
|
||||
parent_slot: self.parent_slot,
|
||||
transactions,
|
||||
signatures,
|
||||
rewards: if show_rewards {
|
||||
rewards: if options.show_rewards {
|
||||
Some(self.rewards)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
block_time: self.block_time,
|
||||
block_height: self.block_height,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -568,21 +609,6 @@ pub struct UiConfirmedBlock {
|
||||
pub block_height: Option<u64>,
|
||||
}
|
||||
|
||||
impl From<EncodedConfirmedBlock> for UiConfirmedBlock {
|
||||
fn from(block: EncodedConfirmedBlock) -> Self {
|
||||
Self {
|
||||
previous_blockhash: block.previous_blockhash,
|
||||
blockhash: block.blockhash,
|
||||
parent_slot: block.parent_slot,
|
||||
transactions: Some(block.transactions),
|
||||
signatures: None,
|
||||
rewards: Some(block.rewards),
|
||||
block_time: block.block_time,
|
||||
block_height: block.block_height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum TransactionWithStatusMeta {
|
||||
@@ -613,6 +639,23 @@ impl TransactionWithStatusMeta {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encode(
|
||||
self,
|
||||
encoding: UiTransactionEncoding,
|
||||
max_supported_transaction_version: Option<u8>,
|
||||
) -> Result<EncodedTransactionWithStatusMeta, EncodeError> {
|
||||
match self {
|
||||
Self::MissingMetadata(ref transaction) => Ok(EncodedTransactionWithStatusMeta {
|
||||
version: None,
|
||||
transaction: transaction.encode(encoding),
|
||||
meta: None,
|
||||
}),
|
||||
Self::Complete(tx_with_meta) => {
|
||||
tx_with_meta.encode(encoding, max_supported_transaction_version)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_legacy_transaction_with_meta(self) -> Option<LegacyTransactionWithStatusMeta> {
|
||||
match self {
|
||||
TransactionWithStatusMeta::MissingMetadata(transaction) => {
|
||||
@@ -626,9 +669,53 @@ impl TransactionWithStatusMeta {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn account_keys(&self) -> AccountKeys {
|
||||
match self {
|
||||
Self::MissingMetadata(tx) => AccountKeys::new(&tx.message.account_keys, None),
|
||||
Self::Complete(tx_with_meta) => tx_with_meta.account_keys(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VersionedTransactionWithStatusMeta {
|
||||
pub fn encode(
|
||||
self,
|
||||
encoding: UiTransactionEncoding,
|
||||
max_supported_transaction_version: Option<u8>,
|
||||
) -> Result<EncodedTransactionWithStatusMeta, EncodeError> {
|
||||
let version = match (
|
||||
max_supported_transaction_version,
|
||||
self.transaction.version(),
|
||||
) {
|
||||
// Set to none because old clients can't handle this field
|
||||
(None, TransactionVersion::LEGACY) => Ok(None),
|
||||
(None, TransactionVersion::Number(version)) => {
|
||||
Err(EncodeError::UnsupportedTransactionVersion(version))
|
||||
}
|
||||
(Some(_), TransactionVersion::LEGACY) => Ok(Some(TransactionVersion::LEGACY)),
|
||||
(Some(max_version), TransactionVersion::Number(version)) => {
|
||||
if version <= max_version {
|
||||
Ok(Some(TransactionVersion::Number(version)))
|
||||
} else {
|
||||
Err(EncodeError::UnsupportedTransactionVersion(version))
|
||||
}
|
||||
}
|
||||
}?;
|
||||
|
||||
Ok(EncodedTransactionWithStatusMeta {
|
||||
transaction: self.transaction.encode_with_meta(encoding, &self.meta),
|
||||
meta: Some(match encoding {
|
||||
UiTransactionEncoding::JsonParsed => UiTransactionStatusMeta::parse(
|
||||
self.meta,
|
||||
self.transaction.message.static_account_keys(),
|
||||
),
|
||||
_ => UiTransactionStatusMeta::from(self.meta),
|
||||
}),
|
||||
version,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn account_keys(&self) -> AccountKeys {
|
||||
AccountKeys::new(
|
||||
self.transaction.message.static_account_keys(),
|
||||
@@ -636,7 +723,7 @@ impl VersionedTransactionWithStatusMeta {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn into_legacy_transaction_with_meta(self) -> Option<LegacyTransactionWithStatusMeta> {
|
||||
fn into_legacy_transaction_with_meta(self) -> Option<LegacyTransactionWithStatusMeta> {
|
||||
Some(LegacyTransactionWithStatusMeta {
|
||||
transaction: self.transaction.into_legacy_transaction()?,
|
||||
meta: Some(self.meta),
|
||||
@@ -644,26 +731,13 @@ impl VersionedTransactionWithStatusMeta {
|
||||
}
|
||||
}
|
||||
|
||||
impl Encodable for LegacyTransactionWithStatusMeta {
|
||||
type Encoded = EncodedTransactionWithStatusMeta;
|
||||
fn encode(self, encoding: UiTransactionEncoding) -> Self::Encoded {
|
||||
Self::Encoded {
|
||||
transaction: self.transaction.encode(encoding),
|
||||
meta: self.meta.map(|meta| match encoding {
|
||||
UiTransactionEncoding::JsonParsed => {
|
||||
UiTransactionStatusMeta::parse(meta, &self.transaction.message)
|
||||
}
|
||||
_ => UiTransactionStatusMeta::from(meta),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EncodedTransactionWithStatusMeta {
|
||||
pub transaction: EncodedTransaction,
|
||||
pub meta: Option<UiTransactionStatusMeta>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub version: Option<TransactionVersion>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
@@ -686,17 +760,6 @@ pub struct LegacyConfirmedTransactionWithStatusMeta {
|
||||
pub block_time: Option<UnixTimestamp>,
|
||||
}
|
||||
|
||||
impl Encodable for LegacyConfirmedTransactionWithStatusMeta {
|
||||
type Encoded = EncodedConfirmedTransactionWithStatusMeta;
|
||||
fn encode(self, encoding: UiTransactionEncoding) -> Self::Encoded {
|
||||
Self::Encoded {
|
||||
slot: self.slot,
|
||||
transaction: self.tx_with_meta.encode(encoding),
|
||||
block_time: self.block_time,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfirmedTransactionWithStatusMeta {
|
||||
/// Downgrades a versioned confirmed transaction into a legacy
|
||||
/// confirmed transaction if it contains a legacy transaction.
|
||||
@@ -709,6 +772,20 @@ impl ConfirmedTransactionWithStatusMeta {
|
||||
slot: self.slot,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn encode(
|
||||
self,
|
||||
encoding: UiTransactionEncoding,
|
||||
max_supported_transaction_version: Option<u8>,
|
||||
) -> Result<EncodedConfirmedTransactionWithStatusMeta, EncodeError> {
|
||||
Ok(EncodedConfirmedTransactionWithStatusMeta {
|
||||
slot: self.slot,
|
||||
transaction: self
|
||||
.tx_with_meta
|
||||
.encode(encoding, max_supported_transaction_version)?,
|
||||
block_time: self.block_time,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
@@ -728,9 +805,41 @@ pub enum EncodedTransaction {
|
||||
Json(UiTransaction),
|
||||
}
|
||||
|
||||
impl Encodable for &Transaction {
|
||||
impl EncodableWithMeta for VersionedTransaction {
|
||||
type Encoded = EncodedTransaction;
|
||||
fn encode(self, encoding: UiTransactionEncoding) -> Self::Encoded {
|
||||
fn encode_with_meta(
|
||||
&self,
|
||||
encoding: UiTransactionEncoding,
|
||||
meta: &TransactionStatusMeta,
|
||||
) -> Self::Encoded {
|
||||
match encoding {
|
||||
UiTransactionEncoding::Binary => EncodedTransaction::LegacyBinary(
|
||||
bs58::encode(bincode::serialize(self).unwrap()).into_string(),
|
||||
),
|
||||
UiTransactionEncoding::Base58 => EncodedTransaction::Binary(
|
||||
bs58::encode(bincode::serialize(self).unwrap()).into_string(),
|
||||
encoding,
|
||||
),
|
||||
UiTransactionEncoding::Base64 => EncodedTransaction::Binary(
|
||||
base64::encode(bincode::serialize(self).unwrap()),
|
||||
encoding,
|
||||
),
|
||||
UiTransactionEncoding::Json | UiTransactionEncoding::JsonParsed => {
|
||||
EncodedTransaction::Json(UiTransaction {
|
||||
signatures: self.signatures.iter().map(ToString::to_string).collect(),
|
||||
message: match &self.message {
|
||||
VersionedMessage::Legacy(message) => message.encode(encoding),
|
||||
VersionedMessage::V0(message) => message.encode_with_meta(encoding, meta),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Encodable for Transaction {
|
||||
type Encoded = EncodedTransaction;
|
||||
fn encode(&self, encoding: UiTransactionEncoding) -> Self::Encoded {
|
||||
match encoding {
|
||||
UiTransactionEncoding::Binary => EncodedTransaction::LegacyBinary(
|
||||
bs58::encode(bincode::serialize(self).unwrap()).into_string(),
|
||||
@@ -793,9 +902,9 @@ pub enum UiMessage {
|
||||
Raw(UiRawMessage),
|
||||
}
|
||||
|
||||
impl Encodable for &Message {
|
||||
impl Encodable for Message {
|
||||
type Encoded = UiMessage;
|
||||
fn encode(self, encoding: UiTransactionEncoding) -> Self::Encoded {
|
||||
fn encode(&self, encoding: UiTransactionEncoding) -> Self::Encoded {
|
||||
if encoding == UiTransactionEncoding::JsonParsed {
|
||||
let account_keys = AccountKeys::new(&self.account_keys, None);
|
||||
UiMessage::Parsed(UiParsedMessage {
|
||||
@@ -806,6 +915,7 @@ impl Encodable for &Message {
|
||||
.iter()
|
||||
.map(|instruction| UiInstruction::parse(instruction, &account_keys))
|
||||
.collect(),
|
||||
address_table_lookups: None,
|
||||
})
|
||||
} else {
|
||||
UiMessage::Raw(UiRawMessage {
|
||||
@@ -813,6 +923,43 @@ impl Encodable for &Message {
|
||||
account_keys: self.account_keys.iter().map(ToString::to_string).collect(),
|
||||
recent_blockhash: self.recent_blockhash.to_string(),
|
||||
instructions: self.instructions.iter().map(Into::into).collect(),
|
||||
address_table_lookups: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EncodableWithMeta for v0::Message {
|
||||
type Encoded = UiMessage;
|
||||
fn encode_with_meta(
|
||||
&self,
|
||||
encoding: UiTransactionEncoding,
|
||||
meta: &TransactionStatusMeta,
|
||||
) -> Self::Encoded {
|
||||
if encoding == UiTransactionEncoding::JsonParsed {
|
||||
let account_keys = AccountKeys::new(&self.account_keys, Some(&meta.loaded_addresses));
|
||||
let loaded_message = LoadedMessage::new_borrowed(self, &meta.loaded_addresses);
|
||||
UiMessage::Parsed(UiParsedMessage {
|
||||
account_keys: parse_static_accounts(&loaded_message),
|
||||
recent_blockhash: self.recent_blockhash.to_string(),
|
||||
instructions: self
|
||||
.instructions
|
||||
.iter()
|
||||
.map(|instruction| UiInstruction::parse(instruction, &account_keys))
|
||||
.collect(),
|
||||
address_table_lookups: Some(
|
||||
self.address_table_lookups.iter().map(Into::into).collect(),
|
||||
),
|
||||
})
|
||||
} else {
|
||||
UiMessage::Raw(UiRawMessage {
|
||||
header: self.header,
|
||||
account_keys: self.account_keys.iter().map(ToString::to_string).collect(),
|
||||
recent_blockhash: self.recent_blockhash.to_string(),
|
||||
instructions: self.instructions.iter().map(Into::into).collect(),
|
||||
address_table_lookups: Some(
|
||||
self.address_table_lookups.iter().map(Into::into).collect(),
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -826,6 +973,27 @@ pub struct UiRawMessage {
|
||||
pub account_keys: Vec<String>,
|
||||
pub recent_blockhash: String,
|
||||
pub instructions: Vec<UiCompiledInstruction>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub address_table_lookups: Option<Vec<UiAddressTableLookup>>,
|
||||
}
|
||||
|
||||
/// A duplicate representation of a MessageAddressTableLookup, in raw format, for pretty JSON serialization
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiAddressTableLookup {
|
||||
pub account_key: String,
|
||||
pub writable_indexes: Vec<u8>,
|
||||
pub readonly_indexes: Vec<u8>,
|
||||
}
|
||||
|
||||
impl From<&MessageAddressTableLookup> for UiAddressTableLookup {
|
||||
fn from(lookup: &MessageAddressTableLookup) -> Self {
|
||||
Self {
|
||||
account_key: lookup.account_key.to_string(),
|
||||
writable_indexes: lookup.writable_indexes.clone(),
|
||||
readonly_indexes: lookup.readonly_indexes.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A duplicate representation of a Message, in parsed format, for pretty JSON serialization
|
||||
@@ -835,6 +1003,7 @@ pub struct UiParsedMessage {
|
||||
pub account_keys: Vec<ParsedAccount>,
|
||||
pub recent_blockhash: String,
|
||||
pub instructions: Vec<UiInstruction>,
|
||||
pub address_table_lookups: Option<Vec<UiAddressTableLookup>>,
|
||||
}
|
||||
|
||||
// A serialized `Vec<TransactionByAddrInfo>` is stored in the `tx-by-addr` table. The row keys are
|
||||
|
@@ -1,4 +1,4 @@
|
||||
use solana_sdk::message::Message;
|
||||
use solana_sdk::message::{v0::LoadedMessage, Message};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -20,16 +20,34 @@ pub fn parse_accounts(message: &Message) -> Vec<ParsedAccount> {
|
||||
accounts
|
||||
}
|
||||
|
||||
pub fn parse_static_accounts(message: &LoadedMessage) -> Vec<ParsedAccount> {
|
||||
let mut accounts: Vec<ParsedAccount> = vec![];
|
||||
for (i, account_key) in message.static_account_keys().iter().enumerate() {
|
||||
accounts.push(ParsedAccount {
|
||||
pubkey: account_key.to_string(),
|
||||
writable: message.is_writable(i),
|
||||
signer: message.is_signer(i),
|
||||
});
|
||||
}
|
||||
accounts
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use {super::*, solana_sdk::message::MessageHeader};
|
||||
use {
|
||||
super::*,
|
||||
solana_sdk::{
|
||||
message::{v0, v0::LoadedAddresses, MessageHeader},
|
||||
pubkey::Pubkey,
|
||||
},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_parse_accounts() {
|
||||
let pubkey0 = solana_sdk::pubkey::new_rand();
|
||||
let pubkey1 = solana_sdk::pubkey::new_rand();
|
||||
let pubkey2 = solana_sdk::pubkey::new_rand();
|
||||
let pubkey3 = solana_sdk::pubkey::new_rand();
|
||||
let pubkey0 = Pubkey::new_unique();
|
||||
let pubkey1 = Pubkey::new_unique();
|
||||
let pubkey2 = Pubkey::new_unique();
|
||||
let pubkey3 = Pubkey::new_unique();
|
||||
let message = Message {
|
||||
header: MessageHeader {
|
||||
num_required_signatures: 2,
|
||||
@@ -66,4 +84,53 @@ mod test {
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_static_accounts() {
|
||||
let pubkey0 = Pubkey::new_unique();
|
||||
let pubkey1 = Pubkey::new_unique();
|
||||
let pubkey2 = Pubkey::new_unique();
|
||||
let pubkey3 = Pubkey::new_unique();
|
||||
let message = LoadedMessage::new(
|
||||
v0::Message {
|
||||
header: MessageHeader {
|
||||
num_required_signatures: 2,
|
||||
num_readonly_signed_accounts: 1,
|
||||
num_readonly_unsigned_accounts: 1,
|
||||
},
|
||||
account_keys: vec![pubkey0, pubkey1, pubkey2, pubkey3],
|
||||
..v0::Message::default()
|
||||
},
|
||||
LoadedAddresses {
|
||||
writable: vec![Pubkey::new_unique()],
|
||||
readonly: vec![Pubkey::new_unique()],
|
||||
},
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parse_static_accounts(&message),
|
||||
vec![
|
||||
ParsedAccount {
|
||||
pubkey: pubkey0.to_string(),
|
||||
writable: true,
|
||||
signer: true,
|
||||
},
|
||||
ParsedAccount {
|
||||
pubkey: pubkey1.to_string(),
|
||||
writable: false,
|
||||
signer: true,
|
||||
},
|
||||
ParsedAccount {
|
||||
pubkey: pubkey2.to_string(),
|
||||
writable: true,
|
||||
signer: false,
|
||||
},
|
||||
ParsedAccount {
|
||||
pubkey: pubkey3.to_string(),
|
||||
writable: false,
|
||||
signer: false,
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user