263 lines
8.1 KiB
Rust
263 lines
8.1 KiB
Rust
#![allow(clippy::integer_arithmetic)]
|
|
#[macro_use]
|
|
extern crate lazy_static;
|
|
#[macro_use]
|
|
extern crate serde_derive;
|
|
|
|
pub mod parse_account_data;
|
|
pub mod parse_bpf_loader;
|
|
pub mod parse_config;
|
|
pub mod parse_nonce;
|
|
pub mod parse_stake;
|
|
pub mod parse_sysvar;
|
|
pub mod parse_token;
|
|
pub mod parse_vote;
|
|
pub mod validator_info;
|
|
|
|
use {
|
|
crate::parse_account_data::{parse_account_data, AccountAdditionalData, ParsedAccount},
|
|
solana_sdk::{
|
|
account::{ReadableAccount, WritableAccount},
|
|
clock::Epoch,
|
|
fee_calculator::FeeCalculator,
|
|
pubkey::Pubkey,
|
|
},
|
|
std::{
|
|
io::{Read, Write},
|
|
str::FromStr,
|
|
},
|
|
};
|
|
|
|
pub type StringAmount = String;
|
|
pub type StringDecimals = String;
|
|
pub const MAX_BASE58_BYTES: usize = 128;
|
|
|
|
/// A duplicate representation of an Account for pretty JSON serialization
|
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct UiAccount {
|
|
pub lamports: u64,
|
|
pub data: UiAccountData,
|
|
pub owner: String,
|
|
pub executable: bool,
|
|
pub rent_epoch: Epoch,
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase", untagged)]
|
|
pub enum UiAccountData {
|
|
LegacyBinary(String), // Legacy. Retained for RPC backwards compatibility
|
|
Json(ParsedAccount),
|
|
Binary(String, UiAccountEncoding),
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub enum UiAccountEncoding {
|
|
Binary, // Legacy. Retained for RPC backwards compatibility
|
|
Base58,
|
|
Base64,
|
|
JsonParsed,
|
|
#[serde(rename = "base64+zstd")]
|
|
Base64Zstd,
|
|
}
|
|
|
|
impl UiAccount {
|
|
fn encode_bs58<T: ReadableAccount>(
|
|
account: &T,
|
|
data_slice_config: Option<UiDataSliceConfig>,
|
|
) -> String {
|
|
if account.data().len() <= MAX_BASE58_BYTES {
|
|
bs58::encode(slice_data(account.data(), data_slice_config)).into_string()
|
|
} else {
|
|
"error: data too large for bs58 encoding".to_string()
|
|
}
|
|
}
|
|
|
|
pub fn encode<T: ReadableAccount>(
|
|
pubkey: &Pubkey,
|
|
account: &T,
|
|
encoding: UiAccountEncoding,
|
|
additional_data: Option<AccountAdditionalData>,
|
|
data_slice_config: Option<UiDataSliceConfig>,
|
|
) -> Self {
|
|
let data = match encoding {
|
|
UiAccountEncoding::Binary => {
|
|
let data = Self::encode_bs58(account, data_slice_config);
|
|
UiAccountData::LegacyBinary(data)
|
|
}
|
|
UiAccountEncoding::Base58 => {
|
|
let data = Self::encode_bs58(account, data_slice_config);
|
|
UiAccountData::Binary(data, encoding)
|
|
}
|
|
UiAccountEncoding::Base64 => UiAccountData::Binary(
|
|
base64::encode(slice_data(account.data(), data_slice_config)),
|
|
encoding,
|
|
),
|
|
UiAccountEncoding::Base64Zstd => {
|
|
let mut encoder = zstd::stream::write::Encoder::new(Vec::new(), 0).unwrap();
|
|
match encoder
|
|
.write_all(slice_data(account.data(), data_slice_config))
|
|
.and_then(|()| encoder.finish())
|
|
{
|
|
Ok(zstd_data) => UiAccountData::Binary(base64::encode(zstd_data), encoding),
|
|
Err(_) => UiAccountData::Binary(
|
|
base64::encode(slice_data(account.data(), data_slice_config)),
|
|
UiAccountEncoding::Base64,
|
|
),
|
|
}
|
|
}
|
|
UiAccountEncoding::JsonParsed => {
|
|
if let Ok(parsed_data) =
|
|
parse_account_data(pubkey, account.owner(), account.data(), additional_data)
|
|
{
|
|
UiAccountData::Json(parsed_data)
|
|
} else {
|
|
UiAccountData::Binary(
|
|
base64::encode(&account.data()),
|
|
UiAccountEncoding::Base64,
|
|
)
|
|
}
|
|
}
|
|
};
|
|
UiAccount {
|
|
lamports: account.lamports(),
|
|
data,
|
|
owner: account.owner().to_string(),
|
|
executable: account.executable(),
|
|
rent_epoch: account.rent_epoch(),
|
|
}
|
|
}
|
|
|
|
pub fn decode<T: WritableAccount>(&self) -> Option<T> {
|
|
let data = match &self.data {
|
|
UiAccountData::Json(_) => None,
|
|
UiAccountData::LegacyBinary(blob) => bs58::decode(blob).into_vec().ok(),
|
|
UiAccountData::Binary(blob, encoding) => match encoding {
|
|
UiAccountEncoding::Base58 => bs58::decode(blob).into_vec().ok(),
|
|
UiAccountEncoding::Base64 => base64::decode(blob).ok(),
|
|
UiAccountEncoding::Base64Zstd => base64::decode(blob)
|
|
.ok()
|
|
.map(|zstd_data| {
|
|
let mut data = vec![];
|
|
zstd::stream::read::Decoder::new(zstd_data.as_slice())
|
|
.and_then(|mut reader| reader.read_to_end(&mut data))
|
|
.map(|_| data)
|
|
.ok()
|
|
})
|
|
.flatten(),
|
|
UiAccountEncoding::Binary | UiAccountEncoding::JsonParsed => None,
|
|
},
|
|
}?;
|
|
Some(T::create(
|
|
self.lamports,
|
|
data,
|
|
Pubkey::from_str(&self.owner).ok()?,
|
|
self.executable,
|
|
self.rent_epoch,
|
|
))
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct UiFeeCalculator {
|
|
pub lamports_per_signature: StringAmount,
|
|
}
|
|
|
|
impl From<FeeCalculator> for UiFeeCalculator {
|
|
fn from(fee_calculator: FeeCalculator) -> Self {
|
|
Self {
|
|
lamports_per_signature: fee_calculator.lamports_per_signature.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for UiFeeCalculator {
|
|
fn default() -> Self {
|
|
Self {
|
|
lamports_per_signature: "0".to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct UiDataSliceConfig {
|
|
pub offset: usize,
|
|
pub length: usize,
|
|
}
|
|
|
|
fn slice_data(data: &[u8], data_slice_config: Option<UiDataSliceConfig>) -> &[u8] {
|
|
if let Some(UiDataSliceConfig { offset, length }) = data_slice_config {
|
|
if offset >= data.len() {
|
|
&[]
|
|
} else if length > data.len() - offset {
|
|
&data[offset..]
|
|
} else {
|
|
&data[offset..offset + length]
|
|
}
|
|
} else {
|
|
data
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use {
|
|
super::*,
|
|
solana_sdk::account::{Account, AccountSharedData},
|
|
};
|
|
|
|
#[test]
|
|
fn test_slice_data() {
|
|
let data = vec![1, 2, 3, 4, 5];
|
|
let slice_config = Some(UiDataSliceConfig {
|
|
offset: 0,
|
|
length: 5,
|
|
});
|
|
assert_eq!(slice_data(&data, slice_config), &data[..]);
|
|
|
|
let slice_config = Some(UiDataSliceConfig {
|
|
offset: 0,
|
|
length: 10,
|
|
});
|
|
assert_eq!(slice_data(&data, slice_config), &data[..]);
|
|
|
|
let slice_config = Some(UiDataSliceConfig {
|
|
offset: 1,
|
|
length: 2,
|
|
});
|
|
assert_eq!(slice_data(&data, slice_config), &data[1..3]);
|
|
|
|
let slice_config = Some(UiDataSliceConfig {
|
|
offset: 10,
|
|
length: 2,
|
|
});
|
|
assert_eq!(slice_data(&data, slice_config), &[] as &[u8]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_base64_zstd() {
|
|
let encoded_account = UiAccount::encode(
|
|
&Pubkey::default(),
|
|
&AccountSharedData::from(Account {
|
|
data: vec![0; 1024],
|
|
..Account::default()
|
|
}),
|
|
UiAccountEncoding::Base64Zstd,
|
|
None,
|
|
None,
|
|
);
|
|
assert!(matches!(
|
|
encoded_account.data,
|
|
UiAccountData::Binary(_, UiAccountEncoding::Base64Zstd)
|
|
));
|
|
|
|
let decoded_account = encoded_account.decode::<Account>().unwrap();
|
|
assert_eq!(decoded_account.data(), &vec![0; 1024]);
|
|
let decoded_account = encoded_account.decode::<AccountSharedData>().unwrap();
|
|
assert_eq!(decoded_account.data(), &vec![0; 1024]);
|
|
}
|
|
}
|