Add serialization-control enum for RPC Options (#27676)
* Add test to verify output when deserializing/reserializing empty fields * Add test to verify serialization from UiTransactionStatusMeta constructors * Add OptionSerializer * Use OptionSerializer for inner_instructions * Use OptionSerializer for loaded_addresses * Remove Default variant, use into instead * Add as_ref implementation * Use OptionSerializer for log_messages and rewards * Use OptionSerializer for token_balances * Use OptionSerializer for return_data * Use OptionSerializer for compute_units_consumed
This commit is contained in:
parent
6a7fbf7aba
commit
360ca07a40
|
@ -263,10 +263,14 @@ fn write_transaction<W: io::Write>(
|
|||
write_status(w, &transaction_status.status, prefix)?;
|
||||
write_fees(w, transaction_status.fee, prefix)?;
|
||||
write_balances(w, transaction_status, prefix)?;
|
||||
write_compute_units_consumed(w, transaction_status.compute_units_consumed, 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)?;
|
||||
write_compute_units_consumed(
|
||||
w,
|
||||
transaction_status.compute_units_consumed.clone().into(),
|
||||
prefix,
|
||||
)?;
|
||||
write_log_messages(w, transaction_status.log_messages.as_ref().into(), prefix)?;
|
||||
write_return_data(w, transaction_status.return_data.as_ref().into(), prefix)?;
|
||||
write_rewards(w, transaction_status.rewards.as_ref().into(), prefix)?;
|
||||
} else {
|
||||
writeln!(w, "{}Status: Unavailable", prefix)?;
|
||||
}
|
||||
|
|
|
@ -31,7 +31,8 @@ use {
|
|||
transaction::{self, Transaction, TransactionError, TransactionVersion},
|
||||
},
|
||||
solana_transaction_status::{
|
||||
EncodedConfirmedBlock, EncodedConfirmedTransactionWithStatusMeta, EncodedTransaction,
|
||||
option_serializer::OptionSerializer, EncodedConfirmedBlock,
|
||||
EncodedConfirmedTransactionWithStatusMeta, EncodedTransaction,
|
||||
EncodedTransactionWithStatusMeta, Rewards, TransactionBinaryEncoding,
|
||||
TransactionConfirmationStatus, TransactionStatus, UiCompiledInstruction, UiMessage,
|
||||
UiRawMessage, UiTransaction, UiTransactionStatusMeta,
|
||||
|
@ -223,14 +224,14 @@ impl RpcSender for MockSender {
|
|||
fee: 0,
|
||||
pre_balances: vec![499999999999999950, 50, 1],
|
||||
post_balances: vec![499999999999999950, 50, 1],
|
||||
inner_instructions: None,
|
||||
log_messages: None,
|
||||
pre_token_balances: None,
|
||||
post_token_balances: None,
|
||||
rewards: None,
|
||||
loaded_addresses: None,
|
||||
return_data: None,
|
||||
compute_units_consumed: None,
|
||||
inner_instructions: OptionSerializer::None,
|
||||
log_messages: OptionSerializer::None,
|
||||
pre_token_balances: OptionSerializer::None,
|
||||
post_token_balances: OptionSerializer::None,
|
||||
rewards: OptionSerializer::None,
|
||||
loaded_addresses: OptionSerializer::Skip,
|
||||
return_data: OptionSerializer::Skip,
|
||||
compute_units_consumed: OptionSerializer::Skip,
|
||||
}),
|
||||
},
|
||||
block_time: Some(1628633791),
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
pub use {crate::extract_memos::extract_and_fmt_memos, solana_sdk::reward_type::RewardType};
|
||||
use {
|
||||
crate::{
|
||||
option_serializer::OptionSerializer,
|
||||
parse_accounts::{parse_legacy_message_accounts, parse_v0_message_accounts, ParsedAccount},
|
||||
parse_instruction::{parse, ParsedInstruction},
|
||||
},
|
||||
|
@ -33,6 +34,7 @@ extern crate lazy_static;
|
|||
extern crate serde_derive;
|
||||
|
||||
pub mod extract_memos;
|
||||
pub mod option_serializer;
|
||||
pub mod parse_accounts;
|
||||
pub mod parse_address_lookup_table;
|
||||
pub mod parse_associated_token;
|
||||
|
@ -250,10 +252,16 @@ pub struct UiTransactionTokenBalance {
|
|||
pub account_index: u8,
|
||||
pub mint: String,
|
||||
pub ui_token_amount: UiTokenAmount,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub owner: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub program_id: Option<String>,
|
||||
#[serde(
|
||||
default = "OptionSerializer::skip",
|
||||
skip_serializing_if = "OptionSerializer::should_skip"
|
||||
)]
|
||||
pub owner: OptionSerializer<String>,
|
||||
#[serde(
|
||||
default = "OptionSerializer::skip",
|
||||
skip_serializing_if = "OptionSerializer::should_skip"
|
||||
)]
|
||||
pub program_id: OptionSerializer<String>,
|
||||
}
|
||||
|
||||
impl From<TransactionTokenBalance> for UiTransactionTokenBalance {
|
||||
|
@ -263,14 +271,14 @@ impl From<TransactionTokenBalance> for UiTransactionTokenBalance {
|
|||
mint: token_balance.mint,
|
||||
ui_token_amount: token_balance.ui_token_amount,
|
||||
owner: if !token_balance.owner.is_empty() {
|
||||
Some(token_balance.owner)
|
||||
OptionSerializer::Some(token_balance.owner)
|
||||
} else {
|
||||
None
|
||||
OptionSerializer::Skip
|
||||
},
|
||||
program_id: if !token_balance.program_id.is_empty() {
|
||||
Some(token_balance.program_id)
|
||||
OptionSerializer::Some(token_balance.program_id)
|
||||
} else {
|
||||
None
|
||||
OptionSerializer::Skip
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -320,17 +328,46 @@ pub struct UiTransactionStatusMeta {
|
|||
pub fee: u64,
|
||||
pub pre_balances: Vec<u64>,
|
||||
pub post_balances: Vec<u64>,
|
||||
pub inner_instructions: Option<Vec<UiInnerInstructions>>,
|
||||
pub log_messages: Option<Vec<String>>,
|
||||
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>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub return_data: Option<UiTransactionReturnData>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub compute_units_consumed: Option<u64>,
|
||||
#[serde(
|
||||
default = "OptionSerializer::none",
|
||||
skip_serializing_if = "OptionSerializer::should_skip"
|
||||
)]
|
||||
pub inner_instructions: OptionSerializer<Vec<UiInnerInstructions>>,
|
||||
#[serde(
|
||||
default = "OptionSerializer::none",
|
||||
skip_serializing_if = "OptionSerializer::should_skip"
|
||||
)]
|
||||
pub log_messages: OptionSerializer<Vec<String>>,
|
||||
#[serde(
|
||||
default = "OptionSerializer::none",
|
||||
skip_serializing_if = "OptionSerializer::should_skip"
|
||||
)]
|
||||
pub pre_token_balances: OptionSerializer<Vec<UiTransactionTokenBalance>>,
|
||||
#[serde(
|
||||
default = "OptionSerializer::none",
|
||||
skip_serializing_if = "OptionSerializer::should_skip"
|
||||
)]
|
||||
pub post_token_balances: OptionSerializer<Vec<UiTransactionTokenBalance>>,
|
||||
#[serde(
|
||||
default = "OptionSerializer::none",
|
||||
skip_serializing_if = "OptionSerializer::should_skip"
|
||||
)]
|
||||
pub rewards: OptionSerializer<Rewards>,
|
||||
#[serde(
|
||||
default = "OptionSerializer::skip",
|
||||
skip_serializing_if = "OptionSerializer::should_skip"
|
||||
)]
|
||||
pub loaded_addresses: OptionSerializer<UiLoadedAddresses>,
|
||||
#[serde(
|
||||
default = "OptionSerializer::skip",
|
||||
skip_serializing_if = "OptionSerializer::should_skip"
|
||||
)]
|
||||
pub return_data: OptionSerializer<UiTransactionReturnData>,
|
||||
#[serde(
|
||||
default = "OptionSerializer::skip",
|
||||
skip_serializing_if = "OptionSerializer::should_skip"
|
||||
)]
|
||||
pub compute_units_consumed: OptionSerializer<u64>,
|
||||
}
|
||||
|
||||
/// A duplicate representation of LoadedAddresses
|
||||
|
@ -367,22 +404,29 @@ impl UiTransactionStatusMeta {
|
|||
fee: meta.fee,
|
||||
pre_balances: meta.pre_balances,
|
||||
post_balances: meta.post_balances,
|
||||
inner_instructions: meta.inner_instructions.map(|ixs| {
|
||||
ixs.into_iter()
|
||||
.map(|ix| UiInnerInstructions::parse(ix, &account_keys))
|
||||
.collect()
|
||||
}),
|
||||
log_messages: meta.log_messages,
|
||||
inner_instructions: meta
|
||||
.inner_instructions
|
||||
.map(|ixs| {
|
||||
ixs.into_iter()
|
||||
.map(|ix| UiInnerInstructions::parse(ix, &account_keys))
|
||||
.collect()
|
||||
})
|
||||
.into(),
|
||||
log_messages: meta.log_messages.into(),
|
||||
pre_token_balances: meta
|
||||
.pre_token_balances
|
||||
.map(|balance| balance.into_iter().map(Into::into).collect()),
|
||||
.map(|balance| balance.into_iter().map(Into::into).collect())
|
||||
.into(),
|
||||
post_token_balances: meta
|
||||
.post_token_balances
|
||||
.map(|balance| balance.into_iter().map(Into::into).collect()),
|
||||
rewards: if show_rewards { meta.rewards } else { None },
|
||||
loaded_addresses: None,
|
||||
return_data: meta.return_data.map(|return_data| return_data.into()),
|
||||
compute_units_consumed: meta.compute_units_consumed,
|
||||
.map(|balance| balance.into_iter().map(Into::into).collect())
|
||||
.into(),
|
||||
rewards: if show_rewards { meta.rewards } else { None }.into(),
|
||||
loaded_addresses: OptionSerializer::Skip,
|
||||
return_data: OptionSerializer::or_skip(
|
||||
meta.return_data.map(|return_data| return_data.into()),
|
||||
),
|
||||
compute_units_consumed: OptionSerializer::or_skip(meta.compute_units_consumed),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -397,18 +441,23 @@ impl From<TransactionStatusMeta> for UiTransactionStatusMeta {
|
|||
post_balances: meta.post_balances,
|
||||
inner_instructions: meta
|
||||
.inner_instructions
|
||||
.map(|ixs| ixs.into_iter().map(Into::into).collect()),
|
||||
log_messages: meta.log_messages,
|
||||
.map(|ixs| ixs.into_iter().map(Into::into).collect())
|
||||
.into(),
|
||||
log_messages: meta.log_messages.into(),
|
||||
pre_token_balances: meta
|
||||
.pre_token_balances
|
||||
.map(|balance| balance.into_iter().map(Into::into).collect()),
|
||||
.map(|balance| balance.into_iter().map(Into::into).collect())
|
||||
.into(),
|
||||
post_token_balances: meta
|
||||
.post_token_balances
|
||||
.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.map(|return_data| return_data.into()),
|
||||
compute_units_consumed: meta.compute_units_consumed,
|
||||
.map(|balance| balance.into_iter().map(Into::into).collect())
|
||||
.into(),
|
||||
rewards: meta.rewards.into(),
|
||||
loaded_addresses: Some(UiLoadedAddresses::from(&meta.loaded_addresses)).into(),
|
||||
return_data: OptionSerializer::or_skip(
|
||||
meta.return_data.map(|return_data| return_data.into()),
|
||||
),
|
||||
compute_units_consumed: OptionSerializer::or_skip(meta.compute_units_consumed),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -723,7 +772,7 @@ impl VersionedTransactionWithStatusMeta {
|
|||
_ => {
|
||||
let mut meta = UiTransactionStatusMeta::from(self.meta);
|
||||
if !show_rewards {
|
||||
meta.rewards = None;
|
||||
meta.rewards = OptionSerializer::None;
|
||||
}
|
||||
meta
|
||||
}
|
||||
|
@ -1039,6 +1088,15 @@ pub struct UiTransactionReturnData {
|
|||
pub data: (String, UiReturnDataEncoding),
|
||||
}
|
||||
|
||||
impl Default for UiTransactionReturnData {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
program_id: String::default(),
|
||||
data: (String::default(), UiReturnDataEncoding::Base64),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TransactionReturnData> for UiTransactionReturnData {
|
||||
fn from(return_data: TransactionReturnData) -> Self {
|
||||
Self {
|
||||
|
@ -1059,7 +1117,7 @@ pub enum UiReturnDataEncoding {
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use {super::*, serde_json::json};
|
||||
|
||||
#[test]
|
||||
fn test_decode_invalid_transaction() {
|
||||
|
@ -1153,4 +1211,134 @@ mod test {
|
|||
};
|
||||
assert!(status.satisfies_commitment(CommitmentConfig::confirmed()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serde_empty_fields() {
|
||||
fn test_serde<'de, T: serde::Serialize + serde::Deserialize<'de>>(
|
||||
json_input: &'de str,
|
||||
expected_json_output: &str,
|
||||
) {
|
||||
let typed_meta: T = serde_json::from_str(json_input).unwrap();
|
||||
let reserialized_value = json!(typed_meta);
|
||||
|
||||
let expected_json_output_value: serde_json::Value =
|
||||
serde_json::from_str(expected_json_output).unwrap();
|
||||
assert_eq!(reserialized_value, expected_json_output_value);
|
||||
}
|
||||
|
||||
let json_input = "{\
|
||||
\"err\":null,\
|
||||
\"status\":{\"Ok\":null},\
|
||||
\"fee\":1234,\
|
||||
\"preBalances\":[1,2,3],\
|
||||
\"postBalances\":[4,5,6]\
|
||||
}";
|
||||
let expected_json_output = "{\
|
||||
\"err\":null,\
|
||||
\"status\":{\"Ok\":null},\
|
||||
\"fee\":1234,\
|
||||
\"preBalances\":[1,2,3],\
|
||||
\"postBalances\":[4,5,6],\
|
||||
\"innerInstructions\":null,\
|
||||
\"logMessages\":null,\
|
||||
\"preTokenBalances\":null,\
|
||||
\"postTokenBalances\":null,\
|
||||
\"rewards\":null\
|
||||
}";
|
||||
test_serde::<UiTransactionStatusMeta>(json_input, expected_json_output);
|
||||
|
||||
let json_input = "{\
|
||||
\"accountIndex\":5,\
|
||||
\"mint\":\"DXM2yVSouSg1twmQgHLKoSReqXhtUroehWxrTgPmmfWi\",\
|
||||
\"uiTokenAmount\": {
|
||||
\"amount\": \"1\",\
|
||||
\"decimals\": 0,\
|
||||
\"uiAmount\": 1.0,\
|
||||
\"uiAmountString\": \"1\"\
|
||||
}\
|
||||
}";
|
||||
let expected_json_output = "{\
|
||||
\"accountIndex\":5,\
|
||||
\"mint\":\"DXM2yVSouSg1twmQgHLKoSReqXhtUroehWxrTgPmmfWi\",\
|
||||
\"uiTokenAmount\": {
|
||||
\"amount\": \"1\",\
|
||||
\"decimals\": 0,\
|
||||
\"uiAmount\": 1.0,\
|
||||
\"uiAmountString\": \"1\"\
|
||||
}\
|
||||
}";
|
||||
test_serde::<UiTransactionTokenBalance>(json_input, expected_json_output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ui_transaction_status_meta_ctors_serialization() {
|
||||
let meta = TransactionStatusMeta {
|
||||
status: Ok(()),
|
||||
fee: 1234,
|
||||
pre_balances: vec![1, 2, 3],
|
||||
post_balances: vec![4, 5, 6],
|
||||
inner_instructions: None,
|
||||
log_messages: None,
|
||||
pre_token_balances: None,
|
||||
post_token_balances: None,
|
||||
rewards: None,
|
||||
loaded_addresses: LoadedAddresses {
|
||||
writable: vec![],
|
||||
readonly: vec![],
|
||||
},
|
||||
return_data: None,
|
||||
compute_units_consumed: None,
|
||||
};
|
||||
let expected_json_output_value: serde_json::Value = serde_json::from_str(
|
||||
"{\
|
||||
\"err\":null,\
|
||||
\"status\":{\"Ok\":null},\
|
||||
\"fee\":1234,\
|
||||
\"preBalances\":[1,2,3],\
|
||||
\"postBalances\":[4,5,6],\
|
||||
\"innerInstructions\":null,\
|
||||
\"logMessages\":null,\
|
||||
\"preTokenBalances\":null,\
|
||||
\"postTokenBalances\":null,\
|
||||
\"rewards\":null,\
|
||||
\"loadedAddresses\":{\
|
||||
\"readonly\": [],\
|
||||
\"writable\": []\
|
||||
}\
|
||||
}",
|
||||
)
|
||||
.unwrap();
|
||||
let ui_meta_from: UiTransactionStatusMeta = meta.clone().into();
|
||||
assert_eq!(
|
||||
serde_json::to_value(&ui_meta_from).unwrap(),
|
||||
expected_json_output_value
|
||||
);
|
||||
|
||||
let expected_json_output_value: serde_json::Value = serde_json::from_str(
|
||||
"{\
|
||||
\"err\":null,\
|
||||
\"status\":{\"Ok\":null},\
|
||||
\"fee\":1234,\
|
||||
\"preBalances\":[1,2,3],\
|
||||
\"postBalances\":[4,5,6],\
|
||||
\"innerInstructions\":null,\
|
||||
\"logMessages\":null,\
|
||||
\"preTokenBalances\":null,\
|
||||
\"postTokenBalances\":null,\
|
||||
\"rewards\":null\
|
||||
}",
|
||||
)
|
||||
.unwrap();
|
||||
let ui_meta_parse_with_rewards = UiTransactionStatusMeta::parse(meta.clone(), &[], true);
|
||||
assert_eq!(
|
||||
serde_json::to_value(&ui_meta_parse_with_rewards).unwrap(),
|
||||
expected_json_output_value
|
||||
);
|
||||
|
||||
let ui_meta_parse_no_rewards = UiTransactionStatusMeta::parse(meta, &[], false);
|
||||
assert_eq!(
|
||||
serde_json::to_value(&ui_meta_parse_no_rewards).unwrap(),
|
||||
expected_json_output_value
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
use serde::{ser::Error, Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum OptionSerializer<T> {
|
||||
Some(T),
|
||||
None,
|
||||
Skip,
|
||||
}
|
||||
|
||||
impl<T> OptionSerializer<T> {
|
||||
pub fn none() -> Self {
|
||||
Self::None
|
||||
}
|
||||
|
||||
pub fn skip() -> Self {
|
||||
Self::Skip
|
||||
}
|
||||
|
||||
pub fn should_skip(&self) -> bool {
|
||||
matches!(self, Self::Skip)
|
||||
}
|
||||
|
||||
pub fn or_skip(option: Option<T>) -> Self {
|
||||
match option {
|
||||
Option::Some(item) => Self::Some(item),
|
||||
Option::None => Self::Skip,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_ref(&self) -> OptionSerializer<&T> {
|
||||
match self {
|
||||
OptionSerializer::Some(item) => OptionSerializer::Some(item),
|
||||
OptionSerializer::None => OptionSerializer::None,
|
||||
OptionSerializer::Skip => OptionSerializer::Skip,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Option<T>> for OptionSerializer<T> {
|
||||
fn from(option: Option<T>) -> Self {
|
||||
match option {
|
||||
Option::Some(item) => Self::Some(item),
|
||||
Option::None => Self::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<OptionSerializer<T>> for Option<T> {
|
||||
fn from(option: OptionSerializer<T>) -> Self {
|
||||
match option {
|
||||
OptionSerializer::Some(item) => Self::Some(item),
|
||||
_ => Self::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Serialize> Serialize for OptionSerializer<T> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match self {
|
||||
Self::Some(item) => item.serialize(serializer),
|
||||
Self::None => serializer.serialize_none(),
|
||||
Self::Skip => Err(Error::custom("Skip variants should not be serialized")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, T: Deserialize<'de>> Deserialize<'de> for OptionSerializer<T> {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
Option::deserialize(deserializer).map(Into::into)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue