Skip to content
This repository has been archived by the owner on Jan 22, 2025. It is now read-only.

Commit

Permalink
Decode native-program and sysvar accounts (bp #11463) (#11485)
Browse files Browse the repository at this point in the history
* Decode native-program and sysvar accounts (#11463)

* Pass pubkey in to account-decoder for sysvars

* Decode sysvar accounts

* Decode config accounts; move validator-info lower

* Decode stake accounts

* Review comments

* Stringify any account lamports and epochs that can be set to u64::MAX

(cherry picked from commit a9f7686)

# Conflicts:
#	Cargo.lock
#	account-decoder/Cargo.toml

* Fix conflicts

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
Co-authored-by: Tyera Eulberg <tyera@solana.com>
  • Loading branch information
3 people authored Aug 9, 2020
1 parent 8a67504 commit 3e48331
Show file tree
Hide file tree
Showing 16 changed files with 852 additions and 54 deletions.
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions account-decoder/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@ edition = "2018"
base64 = "0.12.3"
bincode = "1.3.1"
bs58 = "0.3.1"
bv = "0.11.1"
Inflector = "0.11.4"
lazy_static = "1.4.0"
solana-config-program = { path = "../programs/config", version = "1.3.2" }
solana-sdk = { path = "../sdk", version = "1.3.2" }
solana-stake-program = { path = "../programs/stake", version = "1.3.2" }
solana-vote-program = { path = "../programs/vote", version = "1.3.2" }
spl-token-v1-0 = { package = "spl-token", version = "1.0.6", features = ["skip-no-mangle"] }
serde = "1.0.112"
Expand Down
31 changes: 29 additions & 2 deletions account-decoder/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ extern crate lazy_static;
extern crate serde_derive;

pub mod parse_account_data;
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};
use solana_sdk::{account::Account, clock::Epoch, pubkey::Pubkey};
use solana_sdk::{account::Account, clock::Epoch, fee_calculator::FeeCalculator, pubkey::Pubkey};
use std::str::FromStr;

pub type StringAmount = String;
Expand Down Expand Up @@ -49,6 +53,7 @@ pub enum UiAccountEncoding {

impl UiAccount {
pub fn encode(
pubkey: &Pubkey,
account: Account,
encoding: UiAccountEncoding,
additional_data: Option<AccountAdditionalData>,
Expand All @@ -58,7 +63,7 @@ impl UiAccount {
UiAccountEncoding::Binary64 => UiAccountData::Binary64(base64::encode(account.data)),
UiAccountEncoding::JsonParsed => {
if let Ok(parsed_data) =
parse_account_data(&account.owner, &account.data, additional_data)
parse_account_data(pubkey, &account.owner, &account.data, additional_data)
{
UiAccountData::Json(parsed_data)
} else {
Expand Down Expand Up @@ -90,3 +95,25 @@ impl UiAccount {
})
}
}

#[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(),
}
}
}
38 changes: 33 additions & 5 deletions account-decoder/src/parse_account_data.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,31 @@
use crate::{
parse_config::parse_config,
parse_nonce::parse_nonce,
parse_stake::parse_stake,
parse_sysvar::parse_sysvar,
parse_token::{parse_token, spl_token_id_v1_0},
parse_vote::parse_vote,
};
use inflector::Inflector;
use serde_json::Value;
use solana_sdk::{instruction::InstructionError, pubkey::Pubkey, system_program};
use solana_sdk::{instruction::InstructionError, pubkey::Pubkey, system_program, sysvar};
use std::collections::HashMap;
use thiserror::Error;

lazy_static! {
static ref CONFIG_PROGRAM_ID: Pubkey = solana_config_program::id();
static ref STAKE_PROGRAM_ID: Pubkey = solana_stake_program::id();
static ref SYSTEM_PROGRAM_ID: Pubkey = system_program::id();
static ref SYSVAR_PROGRAM_ID: Pubkey = sysvar::id();
static ref TOKEN_PROGRAM_ID: Pubkey = spl_token_id_v1_0();
static ref VOTE_PROGRAM_ID: Pubkey = solana_vote_program::id();
pub static ref PARSABLE_PROGRAM_IDS: HashMap<Pubkey, ParsableAccount> = {
let mut m = HashMap::new();
m.insert(*CONFIG_PROGRAM_ID, ParsableAccount::Config);
m.insert(*SYSTEM_PROGRAM_ID, ParsableAccount::Nonce);
m.insert(*TOKEN_PROGRAM_ID, ParsableAccount::SplToken);
m.insert(*STAKE_PROGRAM_ID, ParsableAccount::Stake);
m.insert(*SYSVAR_PROGRAM_ID, ParsableAccount::Sysvar);
m.insert(*VOTE_PROGRAM_ID, ParsableAccount::Vote);
m
};
Expand Down Expand Up @@ -50,8 +59,11 @@ pub struct ParsedAccount {
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum ParsableAccount {
Config,
Nonce,
SplToken,
Stake,
Sysvar,
Vote,
}

Expand All @@ -61,6 +73,7 @@ pub struct AccountAdditionalData {
}

pub fn parse_account_data(
pubkey: &Pubkey,
program_id: &Pubkey,
data: &[u8],
additional_data: Option<AccountAdditionalData>,
Expand All @@ -70,10 +83,13 @@ pub fn parse_account_data(
.ok_or_else(|| ParseAccountError::ProgramNotParsable)?;
let additional_data = additional_data.unwrap_or_default();
let parsed_json = match program_name {
ParsableAccount::Config => serde_json::to_value(parse_config(data, pubkey)?)?,
ParsableAccount::Nonce => serde_json::to_value(parse_nonce(data)?)?,
ParsableAccount::SplToken => {
serde_json::to_value(parse_token(data, additional_data.spl_token_decimals)?)?
}
ParsableAccount::Stake => serde_json::to_value(parse_stake(data)?)?,
ParsableAccount::Sysvar => serde_json::to_value(parse_sysvar(data, pubkey)?)?,
ParsableAccount::Vote => serde_json::to_value(parse_vote(data)?)?,
};
Ok(ParsedAccount {
Expand All @@ -93,21 +109,33 @@ mod test {

#[test]
fn test_parse_account_data() {
let account_pubkey = Pubkey::new_rand();
let other_program = Pubkey::new_rand();
let data = vec![0; 4];
assert!(parse_account_data(&other_program, &data, None).is_err());
assert!(parse_account_data(&account_pubkey, &other_program, &data, None).is_err());

let vote_state = VoteState::default();
let mut vote_account_data: Vec<u8> = vec![0; VoteState::size_of()];
let versioned = VoteStateVersions::Current(Box::new(vote_state));
VoteState::serialize(&versioned, &mut vote_account_data).unwrap();
let parsed =
parse_account_data(&solana_vote_program::id(), &vote_account_data, None).unwrap();
let parsed = parse_account_data(
&account_pubkey,
&solana_vote_program::id(),
&vote_account_data,
None,
)
.unwrap();
assert_eq!(parsed.program, "vote".to_string());

let nonce_data = Versions::new_current(State::Initialized(Data::default()));
let nonce_account_data = bincode::serialize(&nonce_data).unwrap();
let parsed = parse_account_data(&system_program::id(), &nonce_account_data, None).unwrap();
let parsed = parse_account_data(
&account_pubkey,
&system_program::id(),
&nonce_account_data,
None,
)
.unwrap();
assert_eq!(parsed.program, "nonce".to_string());
}
}
146 changes: 146 additions & 0 deletions account-decoder/src/parse_config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
use crate::{
parse_account_data::{ParsableAccount, ParseAccountError},
validator_info,
};
use bincode::deserialize;
use serde_json::Value;
use solana_config_program::{get_config_data, ConfigKeys};
use solana_sdk::pubkey::Pubkey;
use solana_stake_program::config::Config as StakeConfig;

pub fn parse_config(data: &[u8], pubkey: &Pubkey) -> Result<ConfigAccountType, ParseAccountError> {
let parsed_account = if pubkey == &solana_stake_program::config::id() {
get_config_data(data)
.ok()
.and_then(|data| deserialize::<StakeConfig>(data).ok())
.map(|config| ConfigAccountType::StakeConfig(config.into()))
} else {
deserialize::<ConfigKeys>(data).ok().and_then(|key_list| {
if !key_list.keys.is_empty() && key_list.keys[0].0 == validator_info::id() {
parse_config_data::<String>(data, key_list.keys).and_then(|validator_info| {
Some(ConfigAccountType::ValidatorInfo(UiConfig {
keys: validator_info.keys,
config_data: serde_json::from_str(&validator_info.config_data).ok()?,
}))
})
} else {
None
}
})
};
parsed_account.ok_or(ParseAccountError::AccountNotParsable(
ParsableAccount::Config,
))
}

fn parse_config_data<T>(data: &[u8], keys: Vec<(Pubkey, bool)>) -> Option<UiConfig<T>>
where
T: serde::de::DeserializeOwned,
{
let config_data: T = deserialize(&get_config_data(data).ok()?).ok()?;
let keys = keys
.iter()
.map(|key| UiConfigKey {
pubkey: key.0.to_string(),
signer: key.1,
})
.collect();
Some(UiConfig { keys, config_data })
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase", tag = "type", content = "info")]
pub enum ConfigAccountType {
StakeConfig(UiStakeConfig),
ValidatorInfo(UiConfig<Value>),
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UiConfigKey {
pub pubkey: String,
pub signer: bool,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UiStakeConfig {
pub warmup_cooldown_rate: f64,
pub slash_penalty: u8,
}

impl From<StakeConfig> for UiStakeConfig {
fn from(config: StakeConfig) -> Self {
Self {
warmup_cooldown_rate: config.warmup_cooldown_rate,
slash_penalty: config.slash_penalty,
}
}
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UiConfig<T> {
pub keys: Vec<UiConfigKey>,
pub config_data: T,
}

#[cfg(test)]
mod test {
use super::*;
use crate::validator_info::ValidatorInfo;
use serde_json::json;
use solana_config_program::create_config_account;

#[test]
fn test_parse_config() {
let stake_config = StakeConfig {
warmup_cooldown_rate: 0.25,
slash_penalty: 50,
};
let stake_config_account = create_config_account(vec![], &stake_config, 10);
assert_eq!(
parse_config(
&stake_config_account.data,
&solana_stake_program::config::id()
)
.unwrap(),
ConfigAccountType::StakeConfig(UiStakeConfig {
warmup_cooldown_rate: 0.25,
slash_penalty: 50,
}),
);

let validator_info = ValidatorInfo {
info: serde_json::to_string(&json!({
"name": "Solana",
}))
.unwrap(),
};
let info_pubkey = Pubkey::new_rand();
let validator_info_config_account = create_config_account(
vec![(validator_info::id(), false), (info_pubkey, true)],
&validator_info,
10,
);
assert_eq!(
parse_config(&validator_info_config_account.data, &info_pubkey).unwrap(),
ConfigAccountType::ValidatorInfo(UiConfig {
keys: vec![
UiConfigKey {
pubkey: validator_info::id().to_string(),
signer: false,
},
UiConfigKey {
pubkey: info_pubkey.to_string(),
signer: true,
}
],
config_data: serde_json::from_str(r#"{"name":"Solana"}"#).unwrap(),
}),
);

let bad_data = vec![0; 4];
assert!(parse_config(&bad_data, &info_pubkey).is_err());
}
}
11 changes: 6 additions & 5 deletions account-decoder/src/parse_nonce.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use crate::parse_account_data::ParseAccountError;
use crate::{parse_account_data::ParseAccountError, UiFeeCalculator};
use solana_sdk::{
fee_calculator::FeeCalculator,
instruction::InstructionError,
nonce::{state::Versions, State},
};
Expand All @@ -14,7 +13,7 @@ pub fn parse_nonce(data: &[u8]) -> Result<UiNonceState, ParseAccountError> {
State::Initialized(data) => Ok(UiNonceState::Initialized(UiNonceData {
authority: data.authority.to_string(),
blockhash: data.blockhash.to_string(),
fee_calculator: data.fee_calculator,
fee_calculator: data.fee_calculator.into(),
})),
}
}
Expand All @@ -32,7 +31,7 @@ pub enum UiNonceState {
pub struct UiNonceData {
pub authority: String,
pub blockhash: String,
pub fee_calculator: FeeCalculator,
pub fee_calculator: UiFeeCalculator,
}

#[cfg(test)]
Expand All @@ -56,7 +55,9 @@ mod test {
UiNonceState::Initialized(UiNonceData {
authority: Pubkey::default().to_string(),
blockhash: Hash::default().to_string(),
fee_calculator: FeeCalculator::default(),
fee_calculator: UiFeeCalculator {
lamports_per_signature: 0.to_string(),
},
}),
);

Expand Down
Loading

0 comments on commit 3e48331

Please sign in to comment.