Add account-decoder utilities (#10846)

* Fix comment and make less pub

* Add account-decoder crate and use to decode vote and system (nonce) accounts

* Update docs

* Rename RpcAccount struct

* s/Rpc/Display

* Call it jsonParsed and update docs

* Revert "s/Rpc/Display"

This reverts commit 6e7149f503f560f1e9237981058ff05642bb7db5.

* s/Rpc/Ui

* Add tests

* Ui more things

* Comments
This commit is contained in:
Tyera Eulberg 2020-06-30 22:55:11 -06:00 committed by GitHub
parent b89e506cbb
commit d97850f1d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 508 additions and 95 deletions

22
Cargo.lock generated
View File

@ -3629,6 +3629,23 @@ dependencies = [
"winapi 0.3.8",
]
[[package]]
name = "solana-account-decoder"
version = "1.3.0"
dependencies = [
"Inflector",
"bincode",
"bs58 0.3.1",
"lazy_static",
"serde",
"serde_derive",
"serde_json",
"solana-sdk 1.3.0",
"solana-vote-program",
"spl-memo",
"thiserror",
]
[[package]]
name = "solana-accounts-bench"
version = "1.3.0"
@ -3816,6 +3833,7 @@ dependencies = [
"serde",
"serde_derive",
"serde_json",
"solana-account-decoder",
"solana-budget-program",
"solana-clap-utils",
"solana-cli-config",
@ -3866,6 +3884,7 @@ dependencies = [
"serde",
"serde_derive",
"serde_json",
"solana-account-decoder",
"solana-logger",
"solana-net-utils",
"solana-sdk 1.3.0",
@ -3925,6 +3944,7 @@ dependencies = [
"serde_json",
"serial_test",
"serial_test_derive",
"solana-account-decoder",
"solana-bpf-loader-program",
"solana-budget-program",
"solana-clap-utils",
@ -4794,6 +4814,8 @@ dependencies = [
"serde_derive",
"serde_json",
"solana-sdk 1.3.0",
"solana-stake-program",
"solana-vote-program",
"spl-memo",
]

View File

@ -54,6 +54,7 @@ members = [
"sys-tuner",
"tokens",
"transaction-status",
"account-decoder",
"upload-perf",
"net-utils",
"version",

View File

@ -0,0 +1,25 @@
[package]
name = "solana-account-decoder"
version = "1.3.0"
description = "Solana account decoder"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"
homepage = "https://solana.com/"
license = "Apache-2.0"
edition = "2018"
[dependencies]
bincode = "1.2.1"
bs58 = "0.3.1"
Inflector = "0.11.4"
lazy_static = "1.4.0"
solana-sdk = { path = "../sdk", version = "1.3.0" }
solana-vote-program = { path = "../programs/vote", version = "1.3.0" }
spl-memo = "1.0.0"
serde = "1.0.112"
serde_derive = "1.0.103"
serde_json = "1.0.54"
thiserror = "1.0"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@ -0,0 +1,80 @@
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate serde_derive;
pub mod parse_account_data;
pub mod parse_nonce;
pub mod parse_vote;
use crate::parse_account_data::parse_account_data;
use serde_json::Value;
use solana_sdk::{account::Account, clock::Epoch, pubkey::Pubkey};
use std::str::FromStr;
/// 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 {
Binary(String),
Json(Value),
}
impl From<Vec<u8>> for UiAccountData {
fn from(data: Vec<u8>) -> Self {
Self::Binary(bs58::encode(data).into_string())
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(rename_all = "camelCase")]
pub enum UiAccountEncoding {
Binary,
JsonParsed,
}
impl UiAccount {
pub fn encode(account: Account, encoding: UiAccountEncoding) -> Self {
let data = match encoding {
UiAccountEncoding::Binary => account.data.into(),
UiAccountEncoding::JsonParsed => {
if let Ok(parsed_data) = parse_account_data(&account.owner, &account.data) {
UiAccountData::Json(parsed_data)
} else {
account.data.into()
}
}
};
UiAccount {
lamports: account.lamports,
data,
owner: account.owner.to_string(),
executable: account.executable,
rent_epoch: account.rent_epoch,
}
}
pub fn decode(&self) -> Option<Account> {
let data = match &self.data {
UiAccountData::Json(_) => None,
UiAccountData::Binary(blob) => bs58::decode(blob).into_vec().ok(),
}?;
Some(Account {
lamports: self.lamports,
data,
owner: Pubkey::from_str(&self.owner).ok()?,
executable: self.executable,
rent_epoch: self.rent_epoch,
})
}
}

View File

@ -0,0 +1,80 @@
use crate::{parse_nonce::parse_nonce, parse_vote::parse_vote};
use inflector::Inflector;
use serde_json::{json, Value};
use solana_sdk::{instruction::InstructionError, pubkey::Pubkey, system_program};
use std::{collections::HashMap, str::FromStr};
use thiserror::Error;
lazy_static! {
static ref SYSTEM_PROGRAM_ID: Pubkey =
Pubkey::from_str(&system_program::id().to_string()).unwrap();
static ref VOTE_PROGRAM_ID: Pubkey =
Pubkey::from_str(&solana_vote_program::id().to_string()).unwrap();
pub static ref PARSABLE_PROGRAM_IDS: HashMap<Pubkey, ParsableAccount> = {
let mut m = HashMap::new();
m.insert(*SYSTEM_PROGRAM_ID, ParsableAccount::Nonce);
m.insert(*VOTE_PROGRAM_ID, ParsableAccount::Vote);
m
};
}
#[derive(Error, Debug)]
pub enum ParseAccountError {
#[error("Program not parsable")]
ProgramNotParsable,
#[error("Instruction error")]
InstructionError(#[from] InstructionError),
#[error("Serde json error")]
SerdeJsonError(#[from] serde_json::error::Error),
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum ParsableAccount {
Nonce,
Vote,
}
pub fn parse_account_data(program_id: &Pubkey, data: &[u8]) -> Result<Value, ParseAccountError> {
let program_name = PARSABLE_PROGRAM_IDS
.get(program_id)
.ok_or_else(|| ParseAccountError::ProgramNotParsable)?;
let parsed_json = match program_name {
ParsableAccount::Nonce => serde_json::to_value(parse_nonce(data)?)?,
ParsableAccount::Vote => serde_json::to_value(parse_vote(data)?)?,
};
Ok(json!({
format!("{:?}", program_name).to_kebab_case(): parsed_json
}))
}
#[cfg(test)]
mod test {
use super::*;
use solana_sdk::nonce::{
state::{Data, Versions},
State,
};
use solana_vote_program::vote_state::{VoteState, VoteStateVersions};
#[test]
fn test_parse_account_data() {
let other_program = Pubkey::new_rand();
let data = vec![0; 4];
assert!(parse_account_data(&other_program, &data).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).unwrap();
assert!(parsed.as_object().unwrap().contains_key("vote"));
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).unwrap();
assert!(parsed.as_object().unwrap().contains_key("nonce"));
}
}

View File

@ -0,0 +1,66 @@
use crate::parse_account_data::ParseAccountError;
use solana_sdk::{
fee_calculator::FeeCalculator,
instruction::InstructionError,
nonce::{state::Versions, State},
};
pub fn parse_nonce(data: &[u8]) -> Result<UiNonceState, ParseAccountError> {
let nonce_state: Versions = bincode::deserialize(data)
.map_err(|_| ParseAccountError::from(InstructionError::InvalidAccountData))?;
let nonce_state = nonce_state.convert_to_current();
match nonce_state {
State::Uninitialized => Ok(UiNonceState::Uninitialized),
State::Initialized(data) => Ok(UiNonceState::Initialized(UiNonceData {
authority: data.authority.to_string(),
blockhash: data.blockhash.to_string(),
fee_calculator: data.fee_calculator,
})),
}
}
/// A duplicate representation of NonceState for pretty JSON serialization
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub enum UiNonceState {
Uninitialized,
Initialized(UiNonceData),
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UiNonceData {
pub authority: String,
pub blockhash: String,
pub fee_calculator: FeeCalculator,
}
#[cfg(test)]
mod test {
use super::*;
use solana_sdk::{
hash::Hash,
nonce::{
state::{Data, Versions},
State,
},
pubkey::Pubkey,
};
#[test]
fn test_parse_nonce() {
let nonce_data = Versions::new_current(State::Initialized(Data::default()));
let nonce_account_data = bincode::serialize(&nonce_data).unwrap();
assert_eq!(
parse_nonce(&nonce_account_data).unwrap(),
UiNonceState::Initialized(UiNonceData {
authority: Pubkey::default().to_string(),
blockhash: Hash::default().to_string(),
fee_calculator: FeeCalculator::default(),
}),
);
let bad_data = vec![0; 4];
assert!(parse_nonce(&bad_data).is_err());
}
}

View File

@ -0,0 +1,134 @@
use crate::parse_account_data::ParseAccountError;
use solana_sdk::{
clock::{Epoch, Slot},
pubkey::Pubkey,
};
use solana_vote_program::vote_state::{BlockTimestamp, Lockout, VoteState};
pub fn parse_vote(data: &[u8]) -> Result<UiVoteState, ParseAccountError> {
let mut vote_state = VoteState::deserialize(data).map_err(ParseAccountError::from)?;
let epoch_credits = vote_state
.epoch_credits()
.iter()
.map(|(epoch, credits, previous_credits)| UiEpochCredits {
epoch: *epoch,
credits: *credits,
previous_credits: *previous_credits,
})
.collect();
let votes = vote_state
.votes
.iter()
.map(|lockout| UiLockout {
slot: lockout.slot,
confirmation_count: lockout.confirmation_count,
})
.collect();
let authorized_voters = vote_state
.authorized_voters()
.iter()
.map(|(epoch, authorized_voter)| UiAuthorizedVoters {
epoch: *epoch,
authorized_voter: authorized_voter.to_string(),
})
.collect();
let prior_voters = vote_state
.prior_voters()
.buf()
.iter()
.filter(|(pubkey, _, _)| pubkey != &Pubkey::default())
.map(
|(authorized_pubkey, epoch_of_last_authorized_switch, target_epoch)| UiPriorVoters {
authorized_pubkey: authorized_pubkey.to_string(),
epoch_of_last_authorized_switch: *epoch_of_last_authorized_switch,
target_epoch: *target_epoch,
},
)
.collect();
Ok(UiVoteState {
node_pubkey: vote_state.node_pubkey.to_string(),
authorized_withdrawer: vote_state.authorized_withdrawer.to_string(),
commission: vote_state.commission,
votes,
root_slot: vote_state.root_slot,
authorized_voters,
prior_voters,
epoch_credits,
last_timestamp: vote_state.last_timestamp,
})
}
/// A duplicate representation of VoteState for pretty JSON serialization
#[derive(Debug, Serialize, Deserialize, Default, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UiVoteState {
node_pubkey: String,
authorized_withdrawer: String,
commission: u8,
votes: Vec<UiLockout>,
root_slot: Option<Slot>,
authorized_voters: Vec<UiAuthorizedVoters>,
prior_voters: Vec<UiPriorVoters>,
epoch_credits: Vec<UiEpochCredits>,
last_timestamp: BlockTimestamp,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
struct UiLockout {
slot: Slot,
confirmation_count: u32,
}
impl From<&Lockout> for UiLockout {
fn from(lockout: &Lockout) -> Self {
Self {
slot: lockout.slot,
confirmation_count: lockout.confirmation_count,
}
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
struct UiAuthorizedVoters {
epoch: Epoch,
authorized_voter: String,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
struct UiPriorVoters {
authorized_pubkey: String,
epoch_of_last_authorized_switch: Epoch,
target_epoch: Epoch,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
struct UiEpochCredits {
epoch: Epoch,
credits: u64,
previous_credits: u64,
}
#[cfg(test)]
mod test {
use super::*;
use solana_vote_program::vote_state::VoteStateVersions;
#[test]
fn test_parse_vote() {
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 mut expected_vote_state = UiVoteState::default();
expected_vote_state.node_pubkey = Pubkey::default().to_string();
expected_vote_state.authorized_withdrawer = Pubkey::default().to_string();
assert_eq!(parse_vote(&vote_account_data).unwrap(), expected_vote_state,);
let bad_data = vec![0; 4];
assert!(parse_vote(&bad_data).is_err());
}
}

View File

@ -27,6 +27,7 @@ reqwest = { version = "0.10.6", default-features = false, features = ["blocking"
serde = "1.0.112"
serde_derive = "1.0.103"
serde_json = "1.0.54"
solana-account-decoder = { path = "../account-decoder", version = "1.3.0" }
solana-budget-program = { path = "../programs/budget", version = "1.3.0" }
solana-clap-utils = { path = "../clap-utils", version = "1.3.0" }
solana-cli-config = { path = "../cli-config", version = "1.3.0" }

View File

@ -15,6 +15,7 @@ use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
use log::*;
use num_traits::FromPrimitive;
use serde_json::{self, json, Value};
use solana_account_decoder::{UiAccount, UiAccountEncoding};
use solana_budget_program::budget_instruction::{self, BudgetError};
use solana_clap_utils::{
commitment::commitment_arg_with_default, input_parsers::*, input_validators::*,
@ -24,7 +25,7 @@ use solana_client::{
client_error::{ClientError, ClientErrorKind, Result as ClientResult},
rpc_client::RpcClient,
rpc_config::{RpcLargestAccountsFilter, RpcSendTransactionConfig},
rpc_response::{RpcAccount, RpcKeyedAccount},
rpc_response::RpcKeyedAccount,
};
#[cfg(not(test))]
use solana_faucet::faucet::request_airdrop_transaction;
@ -1225,7 +1226,7 @@ fn process_show_account(
let cli_account = CliAccount {
keyed_account: RpcKeyedAccount {
pubkey: account_pubkey.to_string(),
account: RpcAccount::encode(account),
account: UiAccount::encode(account, UiAccountEncoding::Binary),
},
use_lamports_unit,
};

View File

@ -111,9 +111,10 @@ mod tests {
use crate::{nonce::nonce_arg, offline::blockhash_query::BlockhashQuery};
use clap::App;
use serde_json::{self, json, Value};
use solana_account_decoder::{UiAccount, UiAccountEncoding};
use solana_client::{
rpc_request::RpcRequest,
rpc_response::{Response, RpcAccount, RpcFeeCalculator, RpcResponseContext},
rpc_response::{Response, RpcFeeCalculator, RpcResponseContext},
};
use solana_sdk::{
account::Account, fee_calculator::FeeCalculator, hash::hash, nonce, system_program,
@ -349,7 +350,7 @@ mod tests {
)
.unwrap();
let nonce_pubkey = Pubkey::new(&[4u8; 32]);
let rpc_nonce_account = RpcAccount::encode(nonce_account);
let rpc_nonce_account = UiAccount::encode(nonce_account, UiAccountEncoding::Binary);
let get_account_response = json!(Response {
context: RpcResponseContext { slot: 1 },
value: json!(Some(rpc_nonce_account)),

View File

@ -19,9 +19,10 @@ reqwest = { version = "0.10.6", default-features = false, features = ["blocking"
serde = "1.0.112"
serde_derive = "1.0.103"
serde_json = "1.0.54"
solana-transaction-status = { path = "../transaction-status", version = "1.3.0" }
solana-account-decoder = { path = "../account-decoder", version = "1.3.0" }
solana-net-utils = { path = "../net-utils", version = "1.3.0" }
solana-sdk = { path = "../sdk", version = "1.3.0" }
solana-transaction-status = { path = "../transaction-status", version = "1.3.0" }
solana-vote-program = { path = "../programs/vote", version = "1.3.0" }
thiserror = "1.0"
tungstenite = "0.10.1"

View File

@ -11,6 +11,7 @@ use bincode::serialize;
use indicatif::{ProgressBar, ProgressStyle};
use log::*;
use serde_json::{json, Value};
use solana_account_decoder::UiAccount;
use solana_sdk::{
account::Account,
clock::{
@ -440,9 +441,9 @@ impl RpcClient {
let Response {
context,
value: rpc_account,
} = serde_json::from_value::<Response<Option<RpcAccount>>>(result_json)?;
} = serde_json::from_value::<Response<Option<UiAccount>>>(result_json)?;
trace!("Response account {:?} {:?}", pubkey, rpc_account);
let account = rpc_account.and_then(|rpc_account| rpc_account.decode().ok());
let account = rpc_account.and_then(|rpc_account| rpc_account.decode());
Ok(Response {
context,
value: account,

View File

@ -1,3 +1,4 @@
use solana_account_decoder::UiAccountEncoding;
use solana_sdk::{clock::Epoch, commitment_config::CommitmentConfig};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
@ -40,3 +41,11 @@ pub struct RpcInflationConfig {
#[serde(flatten)]
pub commitment: Option<CommitmentConfig>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcAccountInfoConfig {
pub encoding: Option<UiAccountEncoding>,
#[serde(flatten)]
pub commitment: Option<CommitmentConfig>,
}

View File

@ -1,13 +1,12 @@
use crate::{client_error, rpc_request::RpcError};
use crate::client_error;
use solana_account_decoder::UiAccount;
use solana_sdk::{
account::Account,
clock::{Epoch, Slot},
fee_calculator::{FeeCalculator, FeeRateGovernor},
inflation::Inflation,
pubkey::Pubkey,
transaction::{Result, TransactionError},
};
use std::{collections::HashMap, net::SocketAddr, str::FromStr};
use std::{collections::HashMap, net::SocketAddr};
pub type RpcResult<T> = client_error::Result<Response<T>>;
@ -91,7 +90,7 @@ pub struct RpcInflationRate {
#[serde(rename_all = "camelCase")]
pub struct RpcKeyedAccount {
pub pubkey: String,
pub account: RpcAccount,
pub account: UiAccount,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
@ -100,43 +99,6 @@ pub struct RpcSignatureResult {
pub err: Option<TransactionError>,
}
/// A duplicate representation of a Message for pretty JSON serialization
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct RpcAccount {
pub lamports: u64,
pub data: String,
pub owner: String,
pub executable: bool,
pub rent_epoch: Epoch,
}
impl RpcAccount {
pub fn encode(account: Account) -> Self {
RpcAccount {
lamports: account.lamports,
data: bs58::encode(account.data.clone()).into_string(),
owner: account.owner.to_string(),
executable: account.executable,
rent_epoch: account.rent_epoch,
}
}
pub fn decode(&self) -> std::result::Result<Account, RpcError> {
Ok(Account {
lamports: self.lamports,
data: bs58::decode(self.data.clone()).into_vec().map_err(|_| {
RpcError::RpcRequestError("Could not parse encoded account data".to_string())
})?,
owner: Pubkey::from_str(&self.owner).map_err(|_| {
RpcError::RpcRequestError("Could not parse encoded account owner".to_string())
})?,
executable: self.executable,
rent_epoch: self.rent_epoch,
})
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct RpcContactInfo {
/// Pubkey of the node as a base-58 string

View File

@ -42,11 +42,11 @@ regex = "1.3.9"
serde = "1.0.112"
serde_derive = "1.0.103"
serde_json = "1.0.54"
solana-account-decoder = { path = "../account-decoder", version = "1.3.0" }
solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "1.3.0" }
solana-budget-program = { path = "../programs/budget", version = "1.3.0" }
solana-clap-utils = { path = "../clap-utils", version = "1.3.0" }
solana-client = { path = "../client", version = "1.3.0" }
solana-transaction-status = { path = "../transaction-status", version = "1.3.0" }
solana-faucet = { path = "../faucet", version = "1.3.0" }
solana-genesis-programs = { path = "../genesis-programs", version = "1.3.0" }
solana-ledger = { path = "../ledger", version = "1.3.0" }
@ -60,10 +60,11 @@ solana-runtime = { path = "../runtime", version = "1.3.0" }
solana-sdk = { path = "../sdk", version = "1.3.0" }
solana-stake-program = { path = "../programs/stake", version = "1.3.0" }
solana-streamer = { path = "../streamer", version = "1.3.0" }
solana-sys-tuner = { path = "../sys-tuner", version = "1.3.0" }
solana-transaction-status = { path = "../transaction-status", version = "1.3.0" }
solana-version = { path = "../version", version = "1.3.0" }
solana-vote-program = { path = "../programs/vote", version = "1.3.0" }
solana-vote-signer = { path = "../vote-signer", version = "1.3.0" }
solana-sys-tuner = { path = "../sys-tuner", version = "1.3.0" }
tempfile = "3.1.0"
thiserror = "1.0"
tokio = "0.1"

View File

@ -8,6 +8,7 @@ use crate::{
use bincode::serialize;
use jsonrpc_core::{Error, Metadata, Result};
use jsonrpc_derive::rpc;
use solana_account_decoder::{UiAccount, UiAccountEncoding};
use solana_client::{
rpc_config::*,
rpc_request::{
@ -205,10 +206,16 @@ impl JsonRpcRequestProcessor {
pub fn get_account_info(
&self,
pubkey: &Pubkey,
commitment: Option<CommitmentConfig>,
) -> Result<RpcResponse<Option<RpcAccount>>> {
let bank = self.bank(commitment)?;
new_response(&bank, bank.get_account(pubkey).map(RpcAccount::encode))
config: Option<RpcAccountInfoConfig>,
) -> Result<RpcResponse<Option<UiAccount>>> {
let config = config.unwrap_or_default();
let bank = self.bank(config.commitment)?;
let encoding = config.encoding.unwrap_or(UiAccountEncoding::Binary);
new_response(
&bank,
bank.get_account(pubkey)
.map(|account| UiAccount::encode(account, encoding)),
)
}
pub fn get_minimum_balance_for_rent_exemption(
@ -224,15 +231,17 @@ impl JsonRpcRequestProcessor {
pub fn get_program_accounts(
&self,
program_id: &Pubkey,
commitment: Option<CommitmentConfig>,
config: Option<RpcAccountInfoConfig>,
) -> Result<Vec<RpcKeyedAccount>> {
Ok(self
.bank(commitment)?
let config = config.unwrap_or_default();
let bank = self.bank(config.commitment)?;
let encoding = config.encoding.unwrap_or(UiAccountEncoding::Binary);
Ok(bank
.get_program_accounts(Some(&program_id))
.into_iter()
.map(|(pubkey, account)| RpcKeyedAccount {
pubkey: pubkey.to_string(),
account: RpcAccount::encode(account),
account: UiAccount::encode(account, encoding.clone()),
})
.collect())
}
@ -823,15 +832,15 @@ pub trait RpcSol {
&self,
meta: Self::Metadata,
pubkey_str: String,
commitment: Option<CommitmentConfig>,
) -> Result<RpcResponse<Option<RpcAccount>>>;
config: Option<RpcAccountInfoConfig>,
) -> Result<RpcResponse<Option<UiAccount>>>;
#[rpc(meta, name = "getProgramAccounts")]
fn get_program_accounts(
&self,
meta: Self::Metadata,
program_id_str: String,
commitment: Option<CommitmentConfig>,
config: Option<RpcAccountInfoConfig>,
) -> Result<Vec<RpcKeyedAccount>>;
#[rpc(meta, name = "getMinimumBalanceForRentExemption")]
@ -1076,11 +1085,11 @@ impl RpcSol for RpcSolImpl {
&self,
meta: Self::Metadata,
pubkey_str: String,
commitment: Option<CommitmentConfig>,
) -> Result<RpcResponse<Option<RpcAccount>>> {
config: Option<RpcAccountInfoConfig>,
) -> Result<RpcResponse<Option<UiAccount>>> {
debug!("get_account_info rpc request received: {:?}", pubkey_str);
let pubkey = verify_pubkey(pubkey_str)?;
meta.get_account_info(&pubkey, commitment)
meta.get_account_info(&pubkey, config)
}
fn get_minimum_balance_for_rent_exemption(
@ -1100,14 +1109,14 @@ impl RpcSol for RpcSolImpl {
&self,
meta: Self::Metadata,
program_id_str: String,
commitment: Option<CommitmentConfig>,
config: Option<RpcAccountInfoConfig>,
) -> Result<Vec<RpcKeyedAccount>> {
debug!(
"get_program_accounts rpc request received: {:?}",
program_id_str
);
let program_id = verify_pubkey(program_id_str)?;
meta.get_program_accounts(&program_id, commitment)
meta.get_program_accounts(&program_id, config)
}
fn get_inflation_governor(

View File

@ -4,9 +4,8 @@ use crate::rpc_subscriptions::{RpcSubscriptions, RpcVote, SlotInfo};
use jsonrpc_core::{Error, ErrorCode, Result};
use jsonrpc_derive::rpc;
use jsonrpc_pubsub::{typed::Subscriber, Session, SubscriptionId};
use solana_client::rpc_response::{
Response as RpcResponse, RpcAccount, RpcKeyedAccount, RpcSignatureResult,
};
use solana_account_decoder::UiAccount;
use solana_client::rpc_response::{Response as RpcResponse, RpcKeyedAccount, RpcSignatureResult};
#[cfg(test)]
use solana_runtime::bank_forks::BankForks;
use solana_sdk::{
@ -37,7 +36,7 @@ pub trait RpcSolPubSub {
fn account_subscribe(
&self,
meta: Self::Metadata,
subscriber: Subscriber<RpcResponse<RpcAccount>>,
subscriber: Subscriber<RpcResponse<UiAccount>>,
pubkey_str: String,
commitment: Option<CommitmentConfig>,
);
@ -172,7 +171,7 @@ impl RpcSolPubSub for RpcSolPubSubImpl {
fn account_subscribe(
&self,
_meta: Self::Metadata,
subscriber: Subscriber<RpcResponse<RpcAccount>>,
subscriber: Subscriber<RpcResponse<UiAccount>>,
pubkey_str: String,
commitment: Option<CommitmentConfig>,
) {

View File

@ -7,8 +7,9 @@ use jsonrpc_pubsub::{
SubscriptionId,
};
use serde::Serialize;
use solana_account_decoder::{UiAccount, UiAccountEncoding};
use solana_client::rpc_response::{
Response, RpcAccount, RpcKeyedAccount, RpcResponseContext, RpcSignatureResult,
Response, RpcKeyedAccount, RpcResponseContext, RpcSignatureResult,
};
use solana_runtime::{bank::Bank, bank_forks::BankForks, commitment::BlockCommitmentCache};
use solana_sdk::{
@ -90,7 +91,7 @@ struct SubscriptionData<S> {
last_notified_slot: RwLock<Slot>,
}
type RpcAccountSubscriptions =
RwLock<HashMap<Pubkey, HashMap<SubscriptionId, SubscriptionData<Response<RpcAccount>>>>>;
RwLock<HashMap<Pubkey, HashMap<SubscriptionId, SubscriptionData<Response<UiAccount>>>>>;
type RpcProgramSubscriptions =
RwLock<HashMap<Pubkey, HashMap<SubscriptionId, SubscriptionData<Response<RpcKeyedAccount>>>>>;
type RpcSignatureSubscriptions = RwLock<
@ -225,12 +226,18 @@ impl RpcNotifier {
fn filter_account_result(
result: Option<(Account, Slot)>,
last_notified_slot: Slot,
) -> (Box<dyn Iterator<Item = RpcAccount>>, Slot) {
) -> (Box<dyn Iterator<Item = UiAccount>>, Slot) {
if let Some((account, fork)) = result {
// If fork < last_notified_slot this means that we last notified for a fork
// and should notify that the account state has been reverted.
if fork != last_notified_slot {
return (Box::new(iter::once(RpcAccount::encode(account))), fork);
return (
Box::new(iter::once(UiAccount::encode(
account,
UiAccountEncoding::Binary,
))),
fork,
);
}
}
(Box::new(iter::empty()), last_notified_slot)
@ -260,7 +267,7 @@ fn filter_program_results(
.into_iter()
.map(|(pubkey, account)| RpcKeyedAccount {
pubkey: pubkey.to_string(),
account: RpcAccount::encode(account),
account: UiAccount::encode(account, UiAccountEncoding::Binary),
}),
),
last_notified_slot,
@ -449,7 +456,7 @@ impl RpcSubscriptions {
pubkey: Pubkey,
commitment: Option<CommitmentConfig>,
sub_id: SubscriptionId,
subscriber: Subscriber<Response<RpcAccount>>,
subscriber: Subscriber<Response<UiAccount>>,
) {
let commitment_level = commitment
.unwrap_or_else(CommitmentConfig::single)

View File

@ -7,9 +7,10 @@ use jsonrpc_core_client::transports::ws;
use log::*;
use reqwest::{self, header::CONTENT_TYPE};
use serde_json::{json, Value};
use solana_account_decoder::UiAccount;
use solana_client::{
rpc_client::{get_rpc_request_str, RpcClient},
rpc_response::{Response, RpcAccount, RpcSignatureResult},
rpc_response::{Response, RpcSignatureResult},
};
use solana_core::contact_info::ContactInfo;
use solana_core::{rpc_pubsub::gen_client::Client as PubsubClient, validator::TestValidator};
@ -172,7 +173,7 @@ fn test_rpc_subscriptions() {
// Track when subscriptions are ready
let (ready_sender, ready_receiver) = channel::<()>();
// Track account notifications are received
let (account_sender, account_receiver) = channel::<Response<RpcAccount>>();
let (account_sender, account_receiver) = channel::<Response<UiAccount>>();
// Track when status notifications are received
let (status_sender, status_receiver) = channel::<(String, Response<RpcSignatureResult>)>();

View File

@ -137,7 +137,10 @@ Returns all information associated with the account of provided Pubkey
#### Parameters:
* `<string>` - Pubkey of account to query, as base-58 encoded string
* `<object>` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
* `<object>` - (optional) Configuration object containing the following optional fields:
* (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
* (optional) `encoding: <string>` - encoding for Account data, either "binary" or jsonParsed". If parameter not provided, the default encoding is binary.
Parsed-JSON encoding attempts to use program-specific state parsers to return more human-readable and explicit account state data. If parsed-JSON is requested but a parser cannot be found, the field falls back to binary encoding, detectable when the `data` field is type `<string>`.
#### Results:
@ -147,7 +150,7 @@ The result will be an RpcResponse JSON object with `value` equal to:
* `<object>` - otherwise, a JSON object containing:
* `lamports: <u64>`, number of lamports assigned to this account, as a u64
* `owner: <string>`, base-58 encoded Pubkey of the program this account has been assigned to
* `data: <string>`, base-58 encoded data associated with the account
* `data: <string|object>`, data associated with the account, either as base-58 encoded binary data or JSON format `{<program>: <state>}`, depending on encoding parameter
* `executable: <bool>`, boolean indicating if the account contains a program \(and is strictly read-only\)
* `rentEpoch`: <u64>, the epoch at which this account will next owe rent, as u64
@ -155,10 +158,16 @@ The result will be an RpcResponse JSON object with `value` equal to:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getAccountInfo", "params":["2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpdST"]}' http://localhost:8899
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getAccountInfo", "params":["4fYNw3dojWmQ4dXtSGE9epjRGy9pFSx62YypT7avPYvA"]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":{"context":{"slot":1},"value":{"executable":false,"owner":"4uQeVj5tqViQh7yWWGStvkEG1Zmhx6uasJtWCJziofM","lamports":1,"data":"Joig2k8Ax4JPMpWhXRyc2jMa7Wejz4X1xqVi3i7QRkmVj1ChUgNc4VNpGUQePJGBAui3c6886peU9GEbjsyeANN8JGStprwLbLwcw5wpPjuQQb9mwrjVmoDQBjj3MzZKgeHn6wmnQ5k8DBFuoCYKWWsJfH2gv9FvCzrN6K1CRcQZzF","rentEpoch":2}},"id":1}
{"jsonrpc":"2.0","result":{"context":{"slot":1},"value":{"data":"11116bv5nS2h3y12kD1yUKeMZvGcKLSjQgX6BeV7u1FrjeJcKfsHRTPuR3oZ1EioKtYGiYxpxMG5vpbZLsbcBYBEmZZcMKaSoGx9JZeAuWf","executable":false,"lamports":1000000000,"owner":"11111111111111111111111111111111","rentEpoch":2}},"id":1}
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getAccountInfo", "params":["4fYNw3dojWmQ4dXtSGE9epjRGy9pFSx62YypT7avPYvA",{"encoding":"json"}]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":{"context":{"slot":1},"value":{"data":{"nonce":{"initialized":{"authority":"Bbqg1M4YVVfbhEzwA9SpC9FhsaG83YMTYoR4a8oTDLX","blockhash":"3xLP3jK6dVJwpeGeTDYTwdDK3TKchUf1gYYGHa4sF3XJ","feeCalculator":{"lamportsPerSignature":5000}}}},"executable":false,"lamports":1000000000,"owner":"11111111111111111111111111111111","rentEpoch":2}},"id":1}
```
### getBalance
@ -279,9 +288,8 @@ Returns identity and transaction information about a confirmed block in the ledg
#### Parameters:
* `<u64>` - slot, as u64 integer
* `<string>` - (optional) encoding for each returned Transaction, either "json", "jsonParsed", or "binary".
Parsed-JSON encoding attempts to use program-specific instruction parsers to return more human-readable and explicit program data.
If parameter not provided, the default encoding is JSON.
* `<string>` - (optional) encoding for each returned Transaction, either "json", "jsonParsed", or "binary". If parameter not provided, the default encoding is JSON.
Parsed-JSON encoding attempts to use program-specific instruction parsers to return more human-readable and explicit data in the `transaction.message.instructions` list. If parsed-JSON is requested but a parser cannot be found, the instruction falls back to regular JSON encoding (`accounts`, `data`, and `programIdIndex` fields).
#### Results:
@ -400,9 +408,8 @@ Returns transaction details for a confirmed transaction
#### Parameters:
* `<string>` - transaction signature as base-58 encoded string
* `<string>` - (optional) encoding for the returned Transaction, either "json", "jsonParsed", or "binary".
Parsed-JSON encoding attempts to use program-specific instruction parsers to return more human-readable and explicit program data.
If parameter not provided, the default encoding is JSON.
* `<string>` - (optional) encoding for the returned Transaction, either "json", "jsonParsed", or "binary". If parameter not provided, the default encoding is JSON.
Parsed-JSON encoding attempts to use program-specific instruction parsers to return more human-readable and explicit data in the `transaction.message.instructions` list. If parsed-JSON is requested but a parser cannot be found, the instruction falls back to regular JSON encoding (`accounts`, `data`, and `programIdIndex` fields).
#### Results:
@ -779,7 +786,10 @@ Returns all accounts owned by the provided program Pubkey
#### Parameters:
* `<string>` - Pubkey of program, as base-58 encoded string
* `<object>` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
* `<object>` - (optional) Configuration object containing the following optional fields:
* (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
* (optional) `encoding: <string>` - encoding for Account data, either "binary" or jsonParsed". If parameter not provided, the default encoding is binary.
Parsed-JSON encoding attempts to use program-specific state parsers to return more human-readable and explicit account state data. If parsed-JSON is requested but a parser cannot be found, the field falls back to binary encoding, detectable when the `data` field is type `<string>`.
#### Results:
@ -789,7 +799,7 @@ The result field will be an array of JSON objects, which will contain:
* `account: <object>` - a JSON object, with the following sub fields:
* `lamports: <u64>`, number of lamports assigned to this account, as a u64
* `owner: <string>`, base-58 encoded Pubkey of the program this account has been assigned to
* `data: <string>`, base-58 encoded data associated with the account
`data: <string|object>`, data associated with the account, either as base-58 encoded binary data or JSON format `{<program>: <state>}`, depending on encoding parameter
* `executable: <bool>`, boolean indicating if the account contains a program \(and is strictly read-only\)
* `rentEpoch`: <u64>, the epoch at which this account will next owe rent, as u64

View File

@ -14,6 +14,8 @@ bs58 = "0.3.1"
Inflector = "0.11.4"
lazy_static = "1.4.0"
solana-sdk = { path = "../sdk", version = "1.3.0" }
solana-stake-program = { path = "../programs/stake", version = "1.3.0" }
solana-vote-program = { path = "../programs/vote", version = "1.3.0" }
spl-memo = "1.0.0"
serde = "1.0.112"
serde_derive = "1.0.103"

View File

@ -23,7 +23,7 @@ pub enum RpcInstruction {
Parsed(Value),
}
/// A duplicate representation of a Message for pretty JSON serialization
/// A duplicate representation of a CompiledInstruction for pretty JSON serialization
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcCompiledInstruction {

View File

@ -8,7 +8,7 @@ use std::{
lazy_static! {
static ref MEMO_PROGRAM_ID: Pubkey = Pubkey::from_str(&spl_memo::id().to_string()).unwrap();
pub static ref PARSABLE_PROGRAM_IDS: HashMap<Pubkey, ParsableProgram> = {
static ref PARSABLE_PROGRAM_IDS: HashMap<Pubkey, ParsableProgram> = {
let mut m = HashMap::new();
m.insert(*MEMO_PROGRAM_ID, ParsableProgram::SplMemo);
m
@ -17,7 +17,7 @@ lazy_static! {
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum ParsableProgram {
enum ParsableProgram {
SplMemo,
}