Compare commits
77 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
768c6b4bef | ||
|
8bcc04c275 | ||
|
2fd822887f | ||
|
e2c8aa0847 | ||
|
9b049402c9 | ||
|
d0e1779893 | ||
|
929ffc5a4e | ||
|
1f63fb06f1 | ||
|
b49aa125c9 | ||
|
55836d133e | ||
|
277e402d55 | ||
|
0ab8312b23 | ||
|
bc4c5c5a97 | ||
|
1a9aa78129 | ||
|
798a6db915 | ||
|
0a4a3fd37e | ||
|
66242eab41 | ||
|
7f0d4f0656 | ||
|
acba8d6026 | ||
|
1ff9555099 | ||
|
72a13e2a72 | ||
|
74cdfc2213 | ||
|
7b8e5a9f47 | ||
|
80525ac862 | ||
|
c14f98c6fc | ||
|
c6edfc3944 | ||
|
b95c493d66 | ||
|
5871462241 | ||
|
53bb826375 | ||
|
c769bcc418 | ||
|
f06a4c7861 | ||
|
0cae099d12 | ||
|
4bc3653906 | ||
|
3e7050983a | ||
|
9f1bb75445 | ||
|
139bb32dba | ||
|
158f6f3725 | ||
|
e33f9ea6b5 | ||
|
473037db86 | ||
|
b0e14ea83c | ||
|
782a549613 | ||
|
c805f7dc4e | ||
|
782829152e | ||
|
da6f09afb8 | ||
|
004b1b9c3f | ||
|
2f8d0f88d6 | ||
|
177d241160 | ||
|
5323622842 | ||
|
c852923347 | ||
|
5dc4410d58 | ||
|
da4642d634 | ||
|
a264be1791 | ||
|
9aff121949 | ||
|
a7f4d1487a | ||
|
11e43e1654 | ||
|
82be47bc18 | ||
|
6498e4fbf6 | ||
|
9978971bd9 | ||
|
e28ac2c377 | ||
|
ef296aa7db | ||
|
43e7107f65 | ||
|
752fa29390 | ||
|
7bb7b42356 | ||
|
2a7fc744f9 | ||
|
90e3da0389 | ||
|
1a62bcee42 | ||
|
b83a4cae90 | ||
|
05ef21cd3b | ||
|
dfa27b04d7 | ||
|
880b04906e | ||
|
1fe0b1e516 | ||
|
f9fd4bd24c | ||
|
c55a11d160 | ||
|
92118de0e1 | ||
|
0d9802a2cd | ||
|
f6beede01b | ||
|
ff48ea20de |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -23,3 +23,7 @@ log-*/
|
||||
/.idea/
|
||||
/solana.iml
|
||||
/.vscode/
|
||||
|
||||
# fetch-spl.sh artifacts
|
||||
/spl-genesis-args.sh
|
||||
/spl_*.so
|
||||
|
2539
Cargo.lock
generated
2539
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -25,6 +25,7 @@ members = [
|
||||
"log-analyzer",
|
||||
"merkle-tree",
|
||||
"stake-o-matic",
|
||||
"storage-bigtable",
|
||||
"streamer",
|
||||
"measure",
|
||||
"metrics",
|
||||
@@ -64,6 +65,4 @@ members = [
|
||||
|
||||
exclude = [
|
||||
"programs/bpf",
|
||||
"programs/move_loader",
|
||||
"programs/librapay",
|
||||
]
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-account-decoder"
|
||||
version = "1.2.12"
|
||||
version = "1.2.21"
|
||||
description = "Solana account decoder"
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@@ -10,12 +10,16 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.2.1"
|
||||
base64 = "0.12.3"
|
||||
bs58 = "0.3.1"
|
||||
bv = "0.11.1"
|
||||
Inflector = "0.11.4"
|
||||
lazy_static = "1.4.0"
|
||||
solana-sdk = { path = "../sdk", version = "1.2.12" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.2.12" }
|
||||
spl-memo = "1.0.1"
|
||||
solana-config-program = { path = "../programs/config", version = "1.2.21" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.21" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.2.21" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.2.21" }
|
||||
spl-token-v1-0 = { package = "spl-token", version = "1.0.6", features = ["skip-no-mangle"] }
|
||||
serde = "1.0.112"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.54"
|
||||
|
@@ -4,14 +4,20 @@ 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;
|
||||
use serde_json::Value;
|
||||
use solana_sdk::{account::Account, clock::Epoch, pubkey::Pubkey};
|
||||
use crate::parse_account_data::{parse_account_data, AccountAdditionalData, ParsedAccount};
|
||||
use solana_sdk::{account::Account, clock::Epoch, fee_calculator::FeeCalculator, pubkey::Pubkey};
|
||||
use std::str::FromStr;
|
||||
|
||||
pub type StringAmount = String;
|
||||
|
||||
/// A duplicate representation of an Account for pretty JSON serialization
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -27,13 +33,8 @@ pub struct UiAccount {
|
||||
#[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())
|
||||
}
|
||||
Json(ParsedAccount),
|
||||
Binary64(String),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
@@ -41,17 +42,28 @@ impl From<Vec<u8>> for UiAccountData {
|
||||
pub enum UiAccountEncoding {
|
||||
Binary,
|
||||
JsonParsed,
|
||||
Binary64,
|
||||
}
|
||||
|
||||
impl UiAccount {
|
||||
pub fn encode(account: Account, encoding: UiAccountEncoding) -> Self {
|
||||
pub fn encode(
|
||||
pubkey: &Pubkey,
|
||||
account: Account,
|
||||
encoding: UiAccountEncoding,
|
||||
additional_data: Option<AccountAdditionalData>,
|
||||
) -> Self {
|
||||
let data = match encoding {
|
||||
UiAccountEncoding::Binary => account.data.into(),
|
||||
UiAccountEncoding::Binary => {
|
||||
UiAccountData::Binary(bs58::encode(account.data).into_string())
|
||||
}
|
||||
UiAccountEncoding::Binary64 => UiAccountData::Binary64(base64::encode(account.data)),
|
||||
UiAccountEncoding::JsonParsed => {
|
||||
if let Ok(parsed_data) = parse_account_data(&account.owner, &account.data) {
|
||||
if let Ok(parsed_data) =
|
||||
parse_account_data(pubkey, &account.owner, &account.data, additional_data)
|
||||
{
|
||||
UiAccountData::Json(parsed_data)
|
||||
} else {
|
||||
account.data.into()
|
||||
UiAccountData::Binary64(base64::encode(account.data))
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -68,6 +80,7 @@ impl UiAccount {
|
||||
let data = match &self.data {
|
||||
UiAccountData::Json(_) => None,
|
||||
UiAccountData::Binary(blob) => bs58::decode(blob).into_vec().ok(),
|
||||
UiAccountData::Binary64(blob) => base64::decode(blob).ok(),
|
||||
}?;
|
||||
Some(Account {
|
||||
lamports: self.lamports,
|
||||
@@ -78,3 +91,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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,18 +1,31 @@
|
||||
use crate::{parse_nonce::parse_nonce, parse_vote::parse_vote};
|
||||
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::{json, Value};
|
||||
use solana_sdk::{instruction::InstructionError, pubkey::Pubkey, system_program};
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
use serde_json::Value;
|
||||
use solana_sdk::{instruction::InstructionError, pubkey::Pubkey, system_program, sysvar};
|
||||
use std::collections::HashMap;
|
||||
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();
|
||||
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
|
||||
};
|
||||
@@ -20,9 +33,15 @@ lazy_static! {
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ParseAccountError {
|
||||
#[error("{0:?} account not parsable")]
|
||||
AccountNotParsable(ParsableAccount),
|
||||
|
||||
#[error("Program not parsable")]
|
||||
ProgramNotParsable,
|
||||
|
||||
#[error("Additional data required to parse: {0}")]
|
||||
AdditionalDataMissing(String),
|
||||
|
||||
#[error("Instruction error")]
|
||||
InstructionError(#[from] InstructionError),
|
||||
|
||||
@@ -30,24 +49,53 @@ pub enum ParseAccountError {
|
||||
SerdeJsonError(#[from] serde_json::error::Error),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ParsedAccount {
|
||||
pub program: String,
|
||||
pub parsed: Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum ParsableAccount {
|
||||
Config,
|
||||
Nonce,
|
||||
SplToken,
|
||||
Stake,
|
||||
Sysvar,
|
||||
Vote,
|
||||
}
|
||||
|
||||
pub fn parse_account_data(program_id: &Pubkey, data: &[u8]) -> Result<Value, ParseAccountError> {
|
||||
#[derive(Default)]
|
||||
pub struct AccountAdditionalData {
|
||||
pub spl_token_decimals: Option<u8>,
|
||||
}
|
||||
|
||||
pub fn parse_account_data(
|
||||
pubkey: &Pubkey,
|
||||
program_id: &Pubkey,
|
||||
data: &[u8],
|
||||
additional_data: Option<AccountAdditionalData>,
|
||||
) -> Result<ParsedAccount, ParseAccountError> {
|
||||
let program_name = PARSABLE_PROGRAM_IDS
|
||||
.get(program_id)
|
||||
.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(json!({
|
||||
format!("{:?}", program_name).to_kebab_case(): parsed_json
|
||||
}))
|
||||
Ok(ParsedAccount {
|
||||
program: format!("{:?}", program_name).to_kebab_case(),
|
||||
parsed: parsed_json,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -61,20 +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).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).unwrap();
|
||||
assert!(parsed.as_object().unwrap().contains_key("vote"));
|
||||
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).unwrap();
|
||||
assert!(parsed.as_object().unwrap().contains_key("nonce"));
|
||||
let parsed = parse_account_data(
|
||||
&account_pubkey,
|
||||
&system_program::id(),
|
||||
&nonce_account_data,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(parsed.program, "nonce".to_string());
|
||||
}
|
||||
}
|
||||
|
146
account-decoder/src/parse_config.rs
Normal file
146
account-decoder/src/parse_config.rs
Normal file
@@ -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());
|
||||
}
|
||||
}
|
@@ -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},
|
||||
};
|
||||
@@ -14,14 +13,14 @@ 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(),
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
/// A duplicate representation of NonceState for pretty JSON serialization
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(rename_all = "camelCase", tag = "type", content = "info")]
|
||||
pub enum UiNonceState {
|
||||
Uninitialized,
|
||||
Initialized(UiNonceData),
|
||||
@@ -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)]
|
||||
@@ -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(),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
|
236
account-decoder/src/parse_stake.rs
Normal file
236
account-decoder/src/parse_stake.rs
Normal file
@@ -0,0 +1,236 @@
|
||||
use crate::{
|
||||
parse_account_data::{ParsableAccount, ParseAccountError},
|
||||
StringAmount,
|
||||
};
|
||||
use bincode::deserialize;
|
||||
use solana_sdk::clock::{Epoch, UnixTimestamp};
|
||||
use solana_stake_program::stake_state::{Authorized, Delegation, Lockup, Meta, Stake, StakeState};
|
||||
|
||||
pub fn parse_stake(data: &[u8]) -> Result<StakeAccountType, ParseAccountError> {
|
||||
let stake_state: StakeState = deserialize(data)
|
||||
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::Stake))?;
|
||||
let parsed_account = match stake_state {
|
||||
StakeState::Uninitialized => StakeAccountType::Uninitialized,
|
||||
StakeState::Initialized(meta) => StakeAccountType::Initialized(UiStakeAccount {
|
||||
meta: meta.into(),
|
||||
stake: None,
|
||||
}),
|
||||
StakeState::Stake(meta, stake) => StakeAccountType::Delegated(UiStakeAccount {
|
||||
meta: meta.into(),
|
||||
stake: Some(stake.into()),
|
||||
}),
|
||||
StakeState::RewardsPool => StakeAccountType::RewardsPool,
|
||||
};
|
||||
Ok(parsed_account)
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase", tag = "type", content = "info")]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum StakeAccountType {
|
||||
Uninitialized,
|
||||
Initialized(UiStakeAccount),
|
||||
Delegated(UiStakeAccount),
|
||||
RewardsPool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiStakeAccount {
|
||||
pub meta: UiMeta,
|
||||
pub stake: Option<UiStake>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiMeta {
|
||||
pub rent_exempt_reserve: StringAmount,
|
||||
pub authorized: UiAuthorized,
|
||||
pub lockup: UiLockup,
|
||||
}
|
||||
|
||||
impl From<Meta> for UiMeta {
|
||||
fn from(meta: Meta) -> Self {
|
||||
Self {
|
||||
rent_exempt_reserve: meta.rent_exempt_reserve.to_string(),
|
||||
authorized: meta.authorized.into(),
|
||||
lockup: meta.lockup.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiLockup {
|
||||
pub unix_timestamp: UnixTimestamp,
|
||||
pub epoch: Epoch,
|
||||
pub custodian: String,
|
||||
}
|
||||
|
||||
impl From<Lockup> for UiLockup {
|
||||
fn from(lockup: Lockup) -> Self {
|
||||
Self {
|
||||
unix_timestamp: lockup.unix_timestamp,
|
||||
epoch: lockup.epoch,
|
||||
custodian: lockup.custodian.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiAuthorized {
|
||||
pub staker: String,
|
||||
pub withdrawer: String,
|
||||
}
|
||||
|
||||
impl From<Authorized> for UiAuthorized {
|
||||
fn from(authorized: Authorized) -> Self {
|
||||
Self {
|
||||
staker: authorized.staker.to_string(),
|
||||
withdrawer: authorized.withdrawer.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiStake {
|
||||
pub delegation: UiDelegation,
|
||||
pub credits_observed: u64,
|
||||
}
|
||||
|
||||
impl From<Stake> for UiStake {
|
||||
fn from(stake: Stake) -> Self {
|
||||
Self {
|
||||
delegation: stake.delegation.into(),
|
||||
credits_observed: stake.credits_observed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiDelegation {
|
||||
pub voter: String,
|
||||
pub stake: StringAmount,
|
||||
pub activation_epoch: StringAmount,
|
||||
pub deactivation_epoch: StringAmount,
|
||||
pub warmup_cooldown_rate: f64,
|
||||
}
|
||||
|
||||
impl From<Delegation> for UiDelegation {
|
||||
fn from(delegation: Delegation) -> Self {
|
||||
Self {
|
||||
voter: delegation.voter_pubkey.to_string(),
|
||||
stake: delegation.stake.to_string(),
|
||||
activation_epoch: delegation.activation_epoch.to_string(),
|
||||
deactivation_epoch: delegation.deactivation_epoch.to_string(),
|
||||
warmup_cooldown_rate: delegation.warmup_cooldown_rate,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use bincode::serialize;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
|
||||
#[test]
|
||||
fn test_parse_stake() {
|
||||
let stake_state = StakeState::Uninitialized;
|
||||
let stake_data = serialize(&stake_state).unwrap();
|
||||
assert_eq!(
|
||||
parse_stake(&stake_data).unwrap(),
|
||||
StakeAccountType::Uninitialized
|
||||
);
|
||||
|
||||
let pubkey = Pubkey::new_rand();
|
||||
let custodian = Pubkey::new_rand();
|
||||
let authorized = Authorized::auto(&pubkey);
|
||||
let lockup = Lockup {
|
||||
unix_timestamp: 0,
|
||||
epoch: 1,
|
||||
custodian,
|
||||
};
|
||||
let meta = Meta {
|
||||
rent_exempt_reserve: 42,
|
||||
authorized,
|
||||
lockup,
|
||||
};
|
||||
|
||||
let stake_state = StakeState::Initialized(meta);
|
||||
let stake_data = serialize(&stake_state).unwrap();
|
||||
assert_eq!(
|
||||
parse_stake(&stake_data).unwrap(),
|
||||
StakeAccountType::Initialized(UiStakeAccount {
|
||||
meta: UiMeta {
|
||||
rent_exempt_reserve: 42.to_string(),
|
||||
authorized: UiAuthorized {
|
||||
staker: pubkey.to_string(),
|
||||
withdrawer: pubkey.to_string(),
|
||||
},
|
||||
lockup: UiLockup {
|
||||
unix_timestamp: 0,
|
||||
epoch: 1,
|
||||
custodian: custodian.to_string(),
|
||||
}
|
||||
},
|
||||
stake: None,
|
||||
})
|
||||
);
|
||||
|
||||
let voter_pubkey = Pubkey::new_rand();
|
||||
let stake = Stake {
|
||||
delegation: Delegation {
|
||||
voter_pubkey,
|
||||
stake: 20,
|
||||
activation_epoch: 2,
|
||||
deactivation_epoch: std::u64::MAX,
|
||||
warmup_cooldown_rate: 0.25,
|
||||
},
|
||||
credits_observed: 10,
|
||||
};
|
||||
|
||||
let stake_state = StakeState::Stake(meta, stake);
|
||||
let stake_data = serialize(&stake_state).unwrap();
|
||||
assert_eq!(
|
||||
parse_stake(&stake_data).unwrap(),
|
||||
StakeAccountType::Delegated(UiStakeAccount {
|
||||
meta: UiMeta {
|
||||
rent_exempt_reserve: 42.to_string(),
|
||||
authorized: UiAuthorized {
|
||||
staker: pubkey.to_string(),
|
||||
withdrawer: pubkey.to_string(),
|
||||
},
|
||||
lockup: UiLockup {
|
||||
unix_timestamp: 0,
|
||||
epoch: 1,
|
||||
custodian: custodian.to_string(),
|
||||
}
|
||||
},
|
||||
stake: Some(UiStake {
|
||||
delegation: UiDelegation {
|
||||
voter: voter_pubkey.to_string(),
|
||||
stake: 20.to_string(),
|
||||
activation_epoch: 2.to_string(),
|
||||
deactivation_epoch: std::u64::MAX.to_string(),
|
||||
warmup_cooldown_rate: 0.25,
|
||||
},
|
||||
credits_observed: 10,
|
||||
})
|
||||
})
|
||||
);
|
||||
|
||||
let stake_state = StakeState::RewardsPool;
|
||||
let stake_data = serialize(&stake_state).unwrap();
|
||||
assert_eq!(
|
||||
parse_stake(&stake_data).unwrap(),
|
||||
StakeAccountType::RewardsPool
|
||||
);
|
||||
|
||||
let bad_data = vec![1, 2, 3, 4];
|
||||
assert!(parse_stake(&bad_data).is_err());
|
||||
}
|
||||
}
|
328
account-decoder/src/parse_sysvar.rs
Normal file
328
account-decoder/src/parse_sysvar.rs
Normal file
@@ -0,0 +1,328 @@
|
||||
use crate::{
|
||||
parse_account_data::{ParsableAccount, ParseAccountError},
|
||||
StringAmount, UiFeeCalculator,
|
||||
};
|
||||
use bincode::deserialize;
|
||||
use bv::BitVec;
|
||||
use solana_sdk::{
|
||||
clock::{Clock, Epoch, Slot, UnixTimestamp},
|
||||
epoch_schedule::EpochSchedule,
|
||||
pubkey::Pubkey,
|
||||
rent::Rent,
|
||||
slot_hashes::SlotHashes,
|
||||
slot_history::{self, SlotHistory},
|
||||
stake_history::{StakeHistory, StakeHistoryEntry},
|
||||
sysvar::{self, fees::Fees, recent_blockhashes::RecentBlockhashes, rewards::Rewards},
|
||||
};
|
||||
|
||||
pub fn parse_sysvar(data: &[u8], pubkey: &Pubkey) -> Result<SysvarAccountType, ParseAccountError> {
|
||||
let parsed_account = {
|
||||
if pubkey == &sysvar::clock::id() {
|
||||
deserialize::<Clock>(data)
|
||||
.ok()
|
||||
.map(|clock| SysvarAccountType::Clock(clock.into()))
|
||||
} else if pubkey == &sysvar::epoch_schedule::id() {
|
||||
deserialize(data).ok().map(SysvarAccountType::EpochSchedule)
|
||||
} else if pubkey == &sysvar::fees::id() {
|
||||
deserialize::<Fees>(data)
|
||||
.ok()
|
||||
.map(|fees| SysvarAccountType::Fees(fees.into()))
|
||||
} else if pubkey == &sysvar::recent_blockhashes::id() {
|
||||
deserialize::<RecentBlockhashes>(data)
|
||||
.ok()
|
||||
.map(|recent_blockhashes| {
|
||||
let recent_blockhashes = recent_blockhashes
|
||||
.iter()
|
||||
.map(|entry| UiRecentBlockhashesEntry {
|
||||
blockhash: entry.blockhash.to_string(),
|
||||
fee_calculator: entry.fee_calculator.clone().into(),
|
||||
})
|
||||
.collect();
|
||||
SysvarAccountType::RecentBlockhashes(recent_blockhashes)
|
||||
})
|
||||
} else if pubkey == &sysvar::rent::id() {
|
||||
deserialize::<Rent>(data)
|
||||
.ok()
|
||||
.map(|rent| SysvarAccountType::Rent(rent.into()))
|
||||
} else if pubkey == &sysvar::rewards::id() {
|
||||
deserialize::<Rewards>(data)
|
||||
.ok()
|
||||
.map(|rewards| SysvarAccountType::Rewards(rewards.into()))
|
||||
} else if pubkey == &sysvar::slot_hashes::id() {
|
||||
deserialize::<SlotHashes>(data).ok().map(|slot_hashes| {
|
||||
let slot_hashes = slot_hashes
|
||||
.iter()
|
||||
.map(|slot_hash| UiSlotHashEntry {
|
||||
slot: slot_hash.0,
|
||||
hash: slot_hash.1.to_string(),
|
||||
})
|
||||
.collect();
|
||||
SysvarAccountType::SlotHashes(slot_hashes)
|
||||
})
|
||||
} else if pubkey == &sysvar::slot_history::id() {
|
||||
deserialize::<SlotHistory>(data).ok().map(|slot_history| {
|
||||
SysvarAccountType::SlotHistory(UiSlotHistory {
|
||||
next_slot: slot_history.next_slot,
|
||||
bits: format!("{:?}", SlotHistoryBits(slot_history.bits)),
|
||||
})
|
||||
})
|
||||
} else if pubkey == &sysvar::stake_history::id() {
|
||||
deserialize::<StakeHistory>(data).ok().map(|stake_history| {
|
||||
let stake_history = stake_history
|
||||
.iter()
|
||||
.map(|entry| UiStakeHistoryEntry {
|
||||
epoch: entry.0,
|
||||
stake_history: entry.1.clone(),
|
||||
})
|
||||
.collect();
|
||||
SysvarAccountType::StakeHistory(stake_history)
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
parsed_account.ok_or(ParseAccountError::AccountNotParsable(
|
||||
ParsableAccount::Sysvar,
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase", tag = "type", content = "info")]
|
||||
pub enum SysvarAccountType {
|
||||
Clock(UiClock),
|
||||
EpochSchedule(EpochSchedule),
|
||||
Fees(UiFees),
|
||||
RecentBlockhashes(Vec<UiRecentBlockhashesEntry>),
|
||||
Rent(UiRent),
|
||||
Rewards(UiRewards),
|
||||
SlotHashes(Vec<UiSlotHashEntry>),
|
||||
SlotHistory(UiSlotHistory),
|
||||
StakeHistory(Vec<UiStakeHistoryEntry>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiClock {
|
||||
pub slot: Slot,
|
||||
pub epoch: Epoch,
|
||||
pub leader_schedule_epoch: Epoch,
|
||||
pub unix_timestamp: UnixTimestamp,
|
||||
}
|
||||
|
||||
impl From<Clock> for UiClock {
|
||||
fn from(clock: Clock) -> Self {
|
||||
Self {
|
||||
slot: clock.slot,
|
||||
epoch: clock.epoch,
|
||||
leader_schedule_epoch: clock.leader_schedule_epoch,
|
||||
unix_timestamp: clock.unix_timestamp,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiFees {
|
||||
pub fee_calculator: UiFeeCalculator,
|
||||
}
|
||||
impl From<Fees> for UiFees {
|
||||
fn from(fees: Fees) -> Self {
|
||||
Self {
|
||||
fee_calculator: fees.fee_calculator.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiRent {
|
||||
pub lamports_per_byte_year: StringAmount,
|
||||
pub exemption_threshold: f64,
|
||||
pub burn_percent: u8,
|
||||
}
|
||||
|
||||
impl From<Rent> for UiRent {
|
||||
fn from(rent: Rent) -> Self {
|
||||
Self {
|
||||
lamports_per_byte_year: rent.lamports_per_byte_year.to_string(),
|
||||
exemption_threshold: rent.exemption_threshold,
|
||||
burn_percent: rent.burn_percent,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiRewards {
|
||||
pub validator_point_value: f64,
|
||||
}
|
||||
|
||||
impl From<Rewards> for UiRewards {
|
||||
fn from(rewards: Rewards) -> Self {
|
||||
Self {
|
||||
validator_point_value: rewards.validator_point_value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiRecentBlockhashesEntry {
|
||||
pub blockhash: String,
|
||||
pub fee_calculator: UiFeeCalculator,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiSlotHashEntry {
|
||||
pub slot: Slot,
|
||||
pub hash: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiSlotHistory {
|
||||
pub next_slot: Slot,
|
||||
pub bits: String,
|
||||
}
|
||||
|
||||
struct SlotHistoryBits(BitVec<u64>);
|
||||
|
||||
impl std::fmt::Debug for SlotHistoryBits {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
for i in 0..slot_history::MAX_ENTRIES {
|
||||
if self.0.get(i) {
|
||||
write!(f, "1")?;
|
||||
} else {
|
||||
write!(f, "0")?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiStakeHistoryEntry {
|
||||
pub epoch: Epoch,
|
||||
pub stake_history: StakeHistoryEntry,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use solana_sdk::{
|
||||
fee_calculator::FeeCalculator,
|
||||
hash::Hash,
|
||||
sysvar::{recent_blockhashes::IterItem, Sysvar},
|
||||
};
|
||||
use std::iter::FromIterator;
|
||||
|
||||
#[test]
|
||||
fn test_parse_sysvars() {
|
||||
let clock_sysvar = Clock::default().create_account(1);
|
||||
assert_eq!(
|
||||
parse_sysvar(&clock_sysvar.data, &sysvar::clock::id()).unwrap(),
|
||||
SysvarAccountType::Clock(UiClock::default()),
|
||||
);
|
||||
|
||||
let epoch_schedule = EpochSchedule {
|
||||
slots_per_epoch: 12,
|
||||
leader_schedule_slot_offset: 0,
|
||||
warmup: false,
|
||||
first_normal_epoch: 1,
|
||||
first_normal_slot: 12,
|
||||
};
|
||||
let epoch_schedule_sysvar = epoch_schedule.create_account(1);
|
||||
assert_eq!(
|
||||
parse_sysvar(&epoch_schedule_sysvar.data, &sysvar::epoch_schedule::id()).unwrap(),
|
||||
SysvarAccountType::EpochSchedule(epoch_schedule),
|
||||
);
|
||||
|
||||
let fees_sysvar = Fees::default().create_account(1);
|
||||
assert_eq!(
|
||||
parse_sysvar(&fees_sysvar.data, &sysvar::fees::id()).unwrap(),
|
||||
SysvarAccountType::Fees(UiFees::default()),
|
||||
);
|
||||
|
||||
let hash = Hash::new(&[1; 32]);
|
||||
let fee_calculator = FeeCalculator {
|
||||
lamports_per_signature: 10,
|
||||
};
|
||||
let recent_blockhashes =
|
||||
RecentBlockhashes::from_iter(vec![IterItem(0, &hash, &fee_calculator)].into_iter());
|
||||
let recent_blockhashes_sysvar = recent_blockhashes.create_account(1);
|
||||
assert_eq!(
|
||||
parse_sysvar(
|
||||
&recent_blockhashes_sysvar.data,
|
||||
&sysvar::recent_blockhashes::id()
|
||||
)
|
||||
.unwrap(),
|
||||
SysvarAccountType::RecentBlockhashes(vec![UiRecentBlockhashesEntry {
|
||||
blockhash: hash.to_string(),
|
||||
fee_calculator: fee_calculator.into(),
|
||||
}]),
|
||||
);
|
||||
|
||||
let rent = Rent {
|
||||
lamports_per_byte_year: 10,
|
||||
exemption_threshold: 2.0,
|
||||
burn_percent: 5,
|
||||
};
|
||||
let rent_sysvar = rent.create_account(1);
|
||||
assert_eq!(
|
||||
parse_sysvar(&rent_sysvar.data, &sysvar::rent::id()).unwrap(),
|
||||
SysvarAccountType::Rent(rent.into()),
|
||||
);
|
||||
|
||||
let rewards_sysvar = Rewards::default().create_account(1);
|
||||
assert_eq!(
|
||||
parse_sysvar(&rewards_sysvar.data, &sysvar::rewards::id()).unwrap(),
|
||||
SysvarAccountType::Rewards(UiRewards::default()),
|
||||
);
|
||||
|
||||
let mut slot_hashes = SlotHashes::default();
|
||||
slot_hashes.add(1, hash);
|
||||
let slot_hashes_sysvar = slot_hashes.create_account(1);
|
||||
assert_eq!(
|
||||
parse_sysvar(&slot_hashes_sysvar.data, &sysvar::slot_hashes::id()).unwrap(),
|
||||
SysvarAccountType::SlotHashes(vec![UiSlotHashEntry {
|
||||
slot: 1,
|
||||
hash: hash.to_string(),
|
||||
}]),
|
||||
);
|
||||
|
||||
let mut slot_history = SlotHistory::default();
|
||||
slot_history.add(42);
|
||||
let slot_history_sysvar = slot_history.create_account(1);
|
||||
assert_eq!(
|
||||
parse_sysvar(&slot_history_sysvar.data, &sysvar::slot_history::id()).unwrap(),
|
||||
SysvarAccountType::SlotHistory(UiSlotHistory {
|
||||
next_slot: slot_history.next_slot,
|
||||
bits: format!("{:?}", SlotHistoryBits(slot_history.bits)),
|
||||
}),
|
||||
);
|
||||
|
||||
let mut stake_history = StakeHistory::default();
|
||||
let stake_history_entry = StakeHistoryEntry {
|
||||
effective: 10,
|
||||
activating: 2,
|
||||
deactivating: 3,
|
||||
};
|
||||
stake_history.add(1, stake_history_entry.clone());
|
||||
let stake_history_sysvar = stake_history.create_account(1);
|
||||
assert_eq!(
|
||||
parse_sysvar(&stake_history_sysvar.data, &sysvar::stake_history::id()).unwrap(),
|
||||
SysvarAccountType::StakeHistory(vec![UiStakeHistoryEntry {
|
||||
epoch: 1,
|
||||
stake_history: stake_history_entry,
|
||||
}]),
|
||||
);
|
||||
|
||||
let bad_pubkey = Pubkey::new_rand();
|
||||
assert!(parse_sysvar(&stake_history_sysvar.data, &bad_pubkey).is_err());
|
||||
|
||||
let bad_data = vec![0; 4];
|
||||
assert!(parse_sysvar(&bad_data, &sysvar::stake_history::id()).is_err());
|
||||
}
|
||||
}
|
257
account-decoder/src/parse_token.rs
Normal file
257
account-decoder/src/parse_token.rs
Normal file
@@ -0,0 +1,257 @@
|
||||
use crate::{
|
||||
parse_account_data::{ParsableAccount, ParseAccountError},
|
||||
StringAmount,
|
||||
};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use spl_token_v1_0::{
|
||||
option::COption,
|
||||
solana_sdk::pubkey::Pubkey as SplTokenPubkey,
|
||||
state::{unpack, Account, Mint, Multisig},
|
||||
};
|
||||
use std::{mem::size_of, str::FromStr};
|
||||
|
||||
// A helper function to convert spl_token_v1_0::id() as spl_sdk::pubkey::Pubkey to
|
||||
// solana_sdk::pubkey::Pubkey
|
||||
pub fn spl_token_id_v1_0() -> Pubkey {
|
||||
Pubkey::from_str(&spl_token_v1_0::id().to_string()).unwrap()
|
||||
}
|
||||
|
||||
// A helper function to convert spl_token_v1_0::native_mint::id() as spl_sdk::pubkey::Pubkey to
|
||||
// solana_sdk::pubkey::Pubkey
|
||||
pub fn spl_token_v1_0_native_mint() -> Pubkey {
|
||||
Pubkey::from_str(&spl_token_v1_0::native_mint::id().to_string()).unwrap()
|
||||
}
|
||||
|
||||
pub fn parse_token(
|
||||
data: &[u8],
|
||||
mint_decimals: Option<u8>,
|
||||
) -> Result<TokenAccountType, ParseAccountError> {
|
||||
let mut data = data.to_vec();
|
||||
if data.len() == size_of::<Account>() {
|
||||
let account: Account = *unpack(&mut data)
|
||||
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::SplToken))?;
|
||||
let decimals = mint_decimals.ok_or_else(|| {
|
||||
ParseAccountError::AdditionalDataMissing(
|
||||
"no mint_decimals provided to parse spl-token account".to_string(),
|
||||
)
|
||||
})?;
|
||||
Ok(TokenAccountType::Account(UiTokenAccount {
|
||||
mint: account.mint.to_string(),
|
||||
owner: account.owner.to_string(),
|
||||
token_amount: token_amount_to_ui_amount(account.amount, decimals),
|
||||
delegate: match account.delegate {
|
||||
COption::Some(pubkey) => Some(pubkey.to_string()),
|
||||
COption::None => None,
|
||||
},
|
||||
is_initialized: account.is_initialized,
|
||||
is_native: account.is_native,
|
||||
delegated_amount: token_amount_to_ui_amount(account.delegated_amount, decimals),
|
||||
}))
|
||||
} else if data.len() == size_of::<Mint>() {
|
||||
let mint: Mint = *unpack(&mut data)
|
||||
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::SplToken))?;
|
||||
Ok(TokenAccountType::Mint(UiMint {
|
||||
owner: match mint.owner {
|
||||
COption::Some(pubkey) => Some(pubkey.to_string()),
|
||||
COption::None => None,
|
||||
},
|
||||
decimals: mint.decimals,
|
||||
is_initialized: mint.is_initialized,
|
||||
}))
|
||||
} else if data.len() == size_of::<Multisig>() {
|
||||
let multisig: Multisig = *unpack(&mut data)
|
||||
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::SplToken))?;
|
||||
Ok(TokenAccountType::Multisig(UiMultisig {
|
||||
num_required_signers: multisig.m,
|
||||
num_valid_signers: multisig.n,
|
||||
is_initialized: multisig.is_initialized,
|
||||
signers: multisig
|
||||
.signers
|
||||
.iter()
|
||||
.filter_map(|pubkey| {
|
||||
if pubkey != &SplTokenPubkey::default() {
|
||||
Some(pubkey.to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
}))
|
||||
} else {
|
||||
Err(ParseAccountError::AccountNotParsable(
|
||||
ParsableAccount::SplToken,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase", tag = "type", content = "info")]
|
||||
pub enum TokenAccountType {
|
||||
Account(UiTokenAccount),
|
||||
Mint(UiMint),
|
||||
Multisig(UiMultisig),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiTokenAccount {
|
||||
pub mint: String,
|
||||
pub owner: String,
|
||||
pub token_amount: UiTokenAmount,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub delegate: Option<String>,
|
||||
pub is_initialized: bool,
|
||||
pub is_native: bool,
|
||||
#[serde(skip_serializing_if = "UiTokenAmount::is_zero")]
|
||||
pub delegated_amount: UiTokenAmount,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiTokenAmount {
|
||||
pub ui_amount: f64,
|
||||
pub decimals: u8,
|
||||
pub amount: StringAmount,
|
||||
}
|
||||
|
||||
impl UiTokenAmount {
|
||||
fn is_zero(&self) -> bool {
|
||||
if let Ok(amount) = self.amount.parse::<u64>() {
|
||||
amount == 0
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn token_amount_to_ui_amount(amount: u64, decimals: u8) -> UiTokenAmount {
|
||||
// Use `amount_to_ui_amount()` once spl_token is bumped to a version that supports it: https://github.com/solana-labs/solana-program-library/pull/211
|
||||
let amount_decimals = amount as f64 / 10_usize.pow(decimals as u32) as f64;
|
||||
UiTokenAmount {
|
||||
ui_amount: amount_decimals,
|
||||
decimals,
|
||||
amount: amount.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiMint {
|
||||
pub owner: Option<String>,
|
||||
pub decimals: u8,
|
||||
pub is_initialized: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiMultisig {
|
||||
pub num_required_signers: u8,
|
||||
pub num_valid_signers: u8,
|
||||
pub is_initialized: bool,
|
||||
pub signers: Vec<String>,
|
||||
}
|
||||
|
||||
pub fn get_token_account_mint(data: &[u8]) -> Option<Pubkey> {
|
||||
if data.len() == size_of::<Account>() {
|
||||
Some(Pubkey::new(&data[0..32]))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use spl_token_v1_0::state::unpack_unchecked;
|
||||
|
||||
#[test]
|
||||
fn test_parse_token() {
|
||||
let mint_pubkey = SplTokenPubkey::new(&[2; 32]);
|
||||
let owner_pubkey = SplTokenPubkey::new(&[3; 32]);
|
||||
let mut account_data = [0; size_of::<Account>()];
|
||||
let mut account: &mut Account = unpack_unchecked(&mut account_data).unwrap();
|
||||
account.mint = mint_pubkey;
|
||||
account.owner = owner_pubkey;
|
||||
account.amount = 42;
|
||||
account.is_initialized = true;
|
||||
assert!(parse_token(&account_data, None).is_err());
|
||||
assert_eq!(
|
||||
parse_token(&account_data, Some(2)).unwrap(),
|
||||
TokenAccountType::Account(UiTokenAccount {
|
||||
mint: mint_pubkey.to_string(),
|
||||
owner: owner_pubkey.to_string(),
|
||||
token_amount: UiTokenAmount {
|
||||
ui_amount: 0.42,
|
||||
decimals: 2,
|
||||
amount: "42".to_string()
|
||||
},
|
||||
delegate: None,
|
||||
is_initialized: true,
|
||||
is_native: false,
|
||||
delegated_amount: UiTokenAmount {
|
||||
ui_amount: 0.0,
|
||||
decimals: 2,
|
||||
amount: "0".to_string()
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
let mut mint_data = [0; size_of::<Mint>()];
|
||||
let mut mint: &mut Mint = unpack_unchecked(&mut mint_data).unwrap();
|
||||
mint.owner = COption::Some(owner_pubkey);
|
||||
mint.decimals = 3;
|
||||
mint.is_initialized = true;
|
||||
assert_eq!(
|
||||
parse_token(&mint_data, None).unwrap(),
|
||||
TokenAccountType::Mint(UiMint {
|
||||
owner: Some(owner_pubkey.to_string()),
|
||||
decimals: 3,
|
||||
is_initialized: true,
|
||||
}),
|
||||
);
|
||||
|
||||
let signer1 = SplTokenPubkey::new(&[1; 32]);
|
||||
let signer2 = SplTokenPubkey::new(&[2; 32]);
|
||||
let signer3 = SplTokenPubkey::new(&[3; 32]);
|
||||
let mut multisig_data = [0; size_of::<Multisig>()];
|
||||
let mut multisig: &mut Multisig = unpack_unchecked(&mut multisig_data).unwrap();
|
||||
let mut signers = [SplTokenPubkey::default(); 11];
|
||||
signers[0] = signer1;
|
||||
signers[1] = signer2;
|
||||
signers[2] = signer3;
|
||||
multisig.m = 2;
|
||||
multisig.n = 3;
|
||||
multisig.is_initialized = true;
|
||||
multisig.signers = signers;
|
||||
assert_eq!(
|
||||
parse_token(&multisig_data, None).unwrap(),
|
||||
TokenAccountType::Multisig(UiMultisig {
|
||||
num_required_signers: 2,
|
||||
num_valid_signers: 3,
|
||||
is_initialized: true,
|
||||
signers: vec![
|
||||
signer1.to_string(),
|
||||
signer2.to_string(),
|
||||
signer3.to_string()
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
let bad_data = vec![0; 4];
|
||||
assert!(parse_token(&bad_data, None).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_token_account_mint() {
|
||||
let mint_pubkey = SplTokenPubkey::new(&[2; 32]);
|
||||
let mut account_data = [0; size_of::<Account>()];
|
||||
let mut account: &mut Account = unpack_unchecked(&mut account_data).unwrap();
|
||||
account.mint = mint_pubkey;
|
||||
|
||||
let expected_mint_pubkey = Pubkey::new(&[2; 32]);
|
||||
assert_eq!(
|
||||
get_token_account_mint(&account_data),
|
||||
Some(expected_mint_pubkey)
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,19 +1,19 @@
|
||||
use crate::parse_account_data::ParseAccountError;
|
||||
use crate::{parse_account_data::ParseAccountError, StringAmount};
|
||||
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> {
|
||||
pub fn parse_vote(data: &[u8]) -> Result<VoteAccountType, 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,
|
||||
credits: credits.to_string(),
|
||||
previous_credits: previous_credits.to_string(),
|
||||
})
|
||||
.collect();
|
||||
let votes = vote_state
|
||||
@@ -45,7 +45,7 @@ pub fn parse_vote(data: &[u8]) -> Result<UiVoteState, ParseAccountError> {
|
||||
},
|
||||
)
|
||||
.collect();
|
||||
Ok(UiVoteState {
|
||||
Ok(VoteAccountType::Vote(UiVoteState {
|
||||
node_pubkey: vote_state.node_pubkey.to_string(),
|
||||
authorized_withdrawer: vote_state.authorized_withdrawer.to_string(),
|
||||
commission: vote_state.commission,
|
||||
@@ -55,7 +55,14 @@ pub fn parse_vote(data: &[u8]) -> Result<UiVoteState, ParseAccountError> {
|
||||
prior_voters,
|
||||
epoch_credits,
|
||||
last_timestamp: vote_state.last_timestamp,
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
/// A wrapper enum for consistency across programs
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase", tag = "type", content = "info")]
|
||||
pub enum VoteAccountType {
|
||||
Vote(UiVoteState),
|
||||
}
|
||||
|
||||
/// A duplicate representation of VoteState for pretty JSON serialization
|
||||
@@ -108,8 +115,8 @@ struct UiPriorVoters {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct UiEpochCredits {
|
||||
epoch: Epoch,
|
||||
credits: u64,
|
||||
previous_credits: u64,
|
||||
credits: StringAmount,
|
||||
previous_credits: StringAmount,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -126,7 +133,10 @@ mod test {
|
||||
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,);
|
||||
assert_eq!(
|
||||
parse_vote(&vote_account_data).unwrap(),
|
||||
VoteAccountType::Vote(expected_vote_state)
|
||||
);
|
||||
|
||||
let bad_data = vec![0; 4];
|
||||
assert!(parse_vote(&bad_data).is_err());
|
||||
|
18
account-decoder/src/validator_info.rs
Normal file
18
account-decoder/src/validator_info.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
use solana_config_program::ConfigState;
|
||||
|
||||
pub const MAX_SHORT_FIELD_LENGTH: usize = 70;
|
||||
pub const MAX_LONG_FIELD_LENGTH: usize = 300;
|
||||
pub const MAX_VALIDATOR_INFO: u64 = 576;
|
||||
|
||||
solana_sdk::declare_id!("Va1idator1nfo111111111111111111111111111111");
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize, Default)]
|
||||
pub struct ValidatorInfo {
|
||||
pub info: String,
|
||||
}
|
||||
|
||||
impl ConfigState for ValidatorInfo {
|
||||
fn max_space() -> u64 {
|
||||
MAX_VALIDATOR_INFO
|
||||
}
|
||||
}
|
@@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-accounts-bench"
|
||||
version = "1.2.12"
|
||||
version = "1.2.21"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@@ -10,10 +10,10 @@ homepage = "https://solana.com/"
|
||||
[dependencies]
|
||||
log = "0.4.6"
|
||||
rayon = "1.3.0"
|
||||
solana-logger = { path = "../logger", version = "1.2.12" }
|
||||
solana-runtime = { path = "../runtime", version = "1.2.12" }
|
||||
solana-measure = { path = "../measure", version = "1.2.12" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.12" }
|
||||
solana-logger = { path = "../logger", version = "1.2.21" }
|
||||
solana-runtime = { path = "../runtime", version = "1.2.21" }
|
||||
solana-measure = { path = "../measure", version = "1.2.21" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.21" }
|
||||
rand = "0.7.0"
|
||||
clap = "2.33.1"
|
||||
crossbeam-channel = "0.4"
|
||||
|
@@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-banking-bench"
|
||||
version = "1.2.12"
|
||||
version = "1.2.21"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@@ -13,16 +13,16 @@ crossbeam-channel = "0.4"
|
||||
log = "0.4.6"
|
||||
rand = "0.7.0"
|
||||
rayon = "1.3.0"
|
||||
solana-core = { path = "../core", version = "1.2.12" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.12" }
|
||||
solana-streamer = { path = "../streamer", version = "1.2.12" }
|
||||
solana-perf = { path = "../perf", version = "1.2.12" }
|
||||
solana-ledger = { path = "../ledger", version = "1.2.12" }
|
||||
solana-logger = { path = "../logger", version = "1.2.12" }
|
||||
solana-runtime = { path = "../runtime", version = "1.2.12" }
|
||||
solana-measure = { path = "../measure", version = "1.2.12" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.12" }
|
||||
solana-version = { path = "../version", version = "1.2.12" }
|
||||
solana-core = { path = "../core", version = "1.2.21" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.21" }
|
||||
solana-streamer = { path = "../streamer", version = "1.2.21" }
|
||||
solana-perf = { path = "../perf", version = "1.2.21" }
|
||||
solana-ledger = { path = "../ledger", version = "1.2.21" }
|
||||
solana-logger = { path = "../logger", version = "1.2.21" }
|
||||
solana-runtime = { path = "../runtime", version = "1.2.21" }
|
||||
solana-measure = { path = "../measure", version = "1.2.21" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.21" }
|
||||
solana-version = { path = "../version", version = "1.2.21" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-exchange"
|
||||
version = "1.2.12"
|
||||
version = "1.2.21"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@@ -18,21 +18,21 @@ rand = "0.7.0"
|
||||
rayon = "1.3.0"
|
||||
serde_json = "1.0.53"
|
||||
serde_yaml = "0.8.12"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.12" }
|
||||
solana-core = { path = "../core", version = "1.2.12" }
|
||||
solana-genesis = { path = "../genesis", version = "1.2.12" }
|
||||
solana-client = { path = "../client", version = "1.2.12" }
|
||||
solana-faucet = { path = "../faucet", version = "1.2.12" }
|
||||
solana-exchange-program = { path = "../programs/exchange", version = "1.2.12" }
|
||||
solana-logger = { path = "../logger", version = "1.2.12" }
|
||||
solana-metrics = { path = "../metrics", version = "1.2.12" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.2.12" }
|
||||
solana-runtime = { path = "../runtime", version = "1.2.12" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.12" }
|
||||
solana-version = { path = "../version", version = "1.2.12" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.21" }
|
||||
solana-core = { path = "../core", version = "1.2.21" }
|
||||
solana-genesis = { path = "../genesis", version = "1.2.21" }
|
||||
solana-client = { path = "../client", version = "1.2.21" }
|
||||
solana-faucet = { path = "../faucet", version = "1.2.21" }
|
||||
solana-exchange-program = { path = "../programs/exchange", version = "1.2.21" }
|
||||
solana-logger = { path = "../logger", version = "1.2.21" }
|
||||
solana-metrics = { path = "../metrics", version = "1.2.21" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.2.21" }
|
||||
solana-runtime = { path = "../runtime", version = "1.2.21" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.21" }
|
||||
solana-version = { path = "../version", version = "1.2.21" }
|
||||
|
||||
[dev-dependencies]
|
||||
solana-local-cluster = { path = "../local-cluster", version = "1.2.12" }
|
||||
solana-local-cluster = { path = "../local-cluster", version = "1.2.21" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@@ -2,18 +2,18 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-streamer"
|
||||
version = "1.2.12"
|
||||
version = "1.2.21"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33.1"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.12" }
|
||||
solana-streamer = { path = "../streamer", version = "1.2.12" }
|
||||
solana-logger = { path = "../logger", version = "1.2.12" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.2.12" }
|
||||
solana-version = { path = "../version", version = "1.2.12" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.21" }
|
||||
solana-streamer = { path = "../streamer", version = "1.2.21" }
|
||||
solana-logger = { path = "../logger", version = "1.2.21" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.2.21" }
|
||||
solana-version = { path = "../version", version = "1.2.21" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-tps"
|
||||
version = "1.2.12"
|
||||
version = "1.2.21"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@@ -14,28 +14,23 @@ log = "0.4.8"
|
||||
rayon = "1.3.0"
|
||||
serde_json = "1.0.53"
|
||||
serde_yaml = "0.8.12"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.12" }
|
||||
solana-core = { path = "../core", version = "1.2.12" }
|
||||
solana-genesis = { path = "../genesis", version = "1.2.12" }
|
||||
solana-client = { path = "../client", version = "1.2.12" }
|
||||
solana-faucet = { path = "../faucet", version = "1.2.12" }
|
||||
solana-librapay = { path = "../programs/librapay", version = "1.2.12", optional = true }
|
||||
solana-logger = { path = "../logger", version = "1.2.12" }
|
||||
solana-metrics = { path = "../metrics", version = "1.2.12" }
|
||||
solana-measure = { path = "../measure", version = "1.2.12" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.2.12" }
|
||||
solana-runtime = { path = "../runtime", version = "1.2.12" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.12" }
|
||||
solana-move-loader-program = { path = "../programs/move_loader", version = "1.2.12", optional = true }
|
||||
solana-version = { path = "../version", version = "1.2.12" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.21" }
|
||||
solana-core = { path = "../core", version = "1.2.21" }
|
||||
solana-genesis = { path = "../genesis", version = "1.2.21" }
|
||||
solana-client = { path = "../client", version = "1.2.21" }
|
||||
solana-faucet = { path = "../faucet", version = "1.2.21" }
|
||||
solana-logger = { path = "../logger", version = "1.2.21" }
|
||||
solana-metrics = { path = "../metrics", version = "1.2.21" }
|
||||
solana-measure = { path = "../measure", version = "1.2.21" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.2.21" }
|
||||
solana-runtime = { path = "../runtime", version = "1.2.21" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.21" }
|
||||
solana-version = { path = "../version", version = "1.2.21" }
|
||||
|
||||
[dev-dependencies]
|
||||
serial_test = "0.4.0"
|
||||
serial_test_derive = "0.4.0"
|
||||
solana-local-cluster = { path = "../local-cluster", version = "1.2.12" }
|
||||
|
||||
[features]
|
||||
move = ["solana-librapay", "solana-move-loader-program"]
|
||||
solana-local-cluster = { path = "../local-cluster", version = "1.2.21" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@@ -4,8 +4,6 @@ use rayon::prelude::*;
|
||||
use solana_client::perf_utils::{sample_txs, SampleStats};
|
||||
use solana_core::gen_keys::GenKeys;
|
||||
use solana_faucet::faucet::request_airdrop_transaction;
|
||||
#[cfg(feature = "move")]
|
||||
use solana_librapay::{create_genesis, upload_mint_script, upload_payment_script};
|
||||
use solana_measure::measure::Measure;
|
||||
use solana_metrics::{self, datapoint_info};
|
||||
use solana_sdk::{
|
||||
@@ -37,9 +35,6 @@ use std::{
|
||||
const MAX_TX_QUEUE_AGE: u64 =
|
||||
MAX_PROCESSING_AGE as u64 * DEFAULT_TICKS_PER_SLOT / DEFAULT_TICKS_PER_SECOND;
|
||||
|
||||
#[cfg(feature = "move")]
|
||||
use solana_librapay::librapay_transaction;
|
||||
|
||||
pub const MAX_SPENDS_PER_TX: u64 = 4;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -51,8 +46,6 @@ pub type Result<T> = std::result::Result<T, BenchTpsError>;
|
||||
|
||||
pub type SharedTransactions = Arc<RwLock<VecDeque<Vec<(Transaction, u64)>>>>;
|
||||
|
||||
type LibraKeys = (Keypair, Pubkey, Pubkey, Vec<Keypair>);
|
||||
|
||||
fn get_recent_blockhash<T: Client>(client: &T) -> (Hash, FeeCalculator) {
|
||||
loop {
|
||||
match client.get_recent_blockhash_with_commitment(CommitmentConfig::recent()) {
|
||||
@@ -122,7 +115,6 @@ fn generate_chunked_transfers(
|
||||
threads: usize,
|
||||
duration: Duration,
|
||||
sustained: bool,
|
||||
libra_args: Option<LibraKeys>,
|
||||
) {
|
||||
// generate and send transactions for the specified duration
|
||||
let start = Instant::now();
|
||||
@@ -137,7 +129,6 @@ fn generate_chunked_transfers(
|
||||
&dest_keypair_chunks[chunk_index],
|
||||
threads,
|
||||
reclaim_lamports_back_to_source_account,
|
||||
&libra_args,
|
||||
);
|
||||
|
||||
// In sustained mode, overlap the transfers with generation. This has higher average
|
||||
@@ -205,12 +196,7 @@ where
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn do_bench_tps<T>(
|
||||
client: Arc<T>,
|
||||
config: Config,
|
||||
gen_keypairs: Vec<Keypair>,
|
||||
libra_args: Option<LibraKeys>,
|
||||
) -> u64
|
||||
pub fn do_bench_tps<T>(client: Arc<T>, config: Config, gen_keypairs: Vec<Keypair>) -> u64
|
||||
where
|
||||
T: 'static + Client + Send + Sync,
|
||||
{
|
||||
@@ -294,7 +280,6 @@ where
|
||||
threads,
|
||||
duration,
|
||||
sustained,
|
||||
libra_args,
|
||||
);
|
||||
|
||||
// Stop the sampling threads so it will collect the stats
|
||||
@@ -340,52 +325,6 @@ fn metrics_submit_lamport_balance(lamport_balance: u64) {
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "move")]
|
||||
fn generate_move_txs(
|
||||
source: &[&Keypair],
|
||||
dest: &VecDeque<&Keypair>,
|
||||
reclaim: bool,
|
||||
move_keypairs: &[Keypair],
|
||||
libra_pay_program_id: &Pubkey,
|
||||
libra_mint_id: &Pubkey,
|
||||
blockhash: &Hash,
|
||||
) -> Vec<(Transaction, u64)> {
|
||||
let count = move_keypairs.len() / 2;
|
||||
let source_move = &move_keypairs[..count];
|
||||
let dest_move = &move_keypairs[count..];
|
||||
let pairs: Vec<_> = if !reclaim {
|
||||
source_move
|
||||
.iter()
|
||||
.zip(dest_move.iter())
|
||||
.zip(source.iter())
|
||||
.collect()
|
||||
} else {
|
||||
dest_move
|
||||
.iter()
|
||||
.zip(source_move.iter())
|
||||
.zip(dest.iter())
|
||||
.collect()
|
||||
};
|
||||
|
||||
pairs
|
||||
.par_iter()
|
||||
.map(|((from, to), payer)| {
|
||||
(
|
||||
librapay_transaction::transfer(
|
||||
libra_pay_program_id,
|
||||
libra_mint_id,
|
||||
&payer,
|
||||
&from,
|
||||
&to.pubkey(),
|
||||
1,
|
||||
*blockhash,
|
||||
),
|
||||
timestamp(),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn generate_system_txs(
|
||||
source: &[&Keypair],
|
||||
dest: &VecDeque<&Keypair>,
|
||||
@@ -416,7 +355,6 @@ fn generate_txs(
|
||||
dest: &VecDeque<&Keypair>,
|
||||
threads: usize,
|
||||
reclaim: bool,
|
||||
libra_args: &Option<LibraKeys>,
|
||||
) {
|
||||
let blockhash = *blockhash.read().unwrap();
|
||||
let tx_count = source.len();
|
||||
@@ -426,33 +364,7 @@ fn generate_txs(
|
||||
);
|
||||
let signing_start = Instant::now();
|
||||
|
||||
let transactions = if let Some((
|
||||
_libra_genesis_keypair,
|
||||
_libra_pay_program_id,
|
||||
_libra_mint_program_id,
|
||||
_libra_keys,
|
||||
)) = libra_args
|
||||
{
|
||||
#[cfg(not(feature = "move"))]
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#[cfg(feature = "move")]
|
||||
{
|
||||
generate_move_txs(
|
||||
source,
|
||||
dest,
|
||||
reclaim,
|
||||
&_libra_keys,
|
||||
_libra_pay_program_id,
|
||||
&_libra_genesis_keypair.pubkey(),
|
||||
&blockhash,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
generate_system_txs(source, dest, reclaim, &blockhash)
|
||||
};
|
||||
let transactions = generate_system_txs(source, dest, reclaim, &blockhash);
|
||||
|
||||
let duration = signing_start.elapsed();
|
||||
let ns = duration.as_secs() * 1_000_000_000 + u64::from(duration.subsec_nanos());
|
||||
@@ -954,181 +866,13 @@ pub fn generate_keypairs(seed_keypair: &Keypair, count: u64) -> (Vec<Keypair>, u
|
||||
(rnd.gen_n_keypairs(total_keys), extra)
|
||||
}
|
||||
|
||||
#[cfg(feature = "move")]
|
||||
fn fund_move_keys<T: Client>(
|
||||
client: &T,
|
||||
funding_key: &Keypair,
|
||||
keypairs: &[Keypair],
|
||||
total: u64,
|
||||
libra_pay_program_id: &Pubkey,
|
||||
libra_mint_program_id: &Pubkey,
|
||||
libra_genesis_key: &Keypair,
|
||||
) {
|
||||
let (mut blockhash, _fee_calculator) = get_recent_blockhash(client);
|
||||
|
||||
info!("creating the libra funding account..");
|
||||
let libra_funding_key = Keypair::new();
|
||||
let tx = librapay_transaction::create_account(funding_key, &libra_funding_key, 1, blockhash);
|
||||
client
|
||||
.send_and_confirm_message(&[funding_key, &libra_funding_key], tx.message)
|
||||
.unwrap();
|
||||
|
||||
info!("minting to funding keypair");
|
||||
let tx = librapay_transaction::mint_tokens(
|
||||
&libra_mint_program_id,
|
||||
funding_key,
|
||||
libra_genesis_key,
|
||||
&libra_funding_key.pubkey(),
|
||||
total,
|
||||
blockhash,
|
||||
);
|
||||
client
|
||||
.send_and_confirm_message(&[funding_key, libra_genesis_key], tx.message)
|
||||
.unwrap();
|
||||
|
||||
info!("creating {} move accounts...", keypairs.len());
|
||||
let total_len = keypairs.len();
|
||||
let create_len = 5;
|
||||
let mut funding_time = Measure::start("funding_time");
|
||||
for (i, keys) in keypairs.chunks(create_len).enumerate() {
|
||||
if client
|
||||
.get_balance_with_commitment(&keys[0].pubkey(), CommitmentConfig::recent())
|
||||
.unwrap_or(0)
|
||||
> 0
|
||||
{
|
||||
// already created these accounts.
|
||||
break;
|
||||
}
|
||||
|
||||
let keypairs: Vec<_> = keys.iter().map(|k| k).collect();
|
||||
let tx = librapay_transaction::create_accounts(funding_key, &keypairs, 1, blockhash);
|
||||
let ser_size = bincode::serialized_size(&tx).unwrap();
|
||||
let mut keys = vec![funding_key];
|
||||
keys.extend(&keypairs);
|
||||
client.send_and_confirm_message(&keys, tx.message).unwrap();
|
||||
|
||||
if i % 10 == 0 {
|
||||
info!(
|
||||
"created {} accounts of {} (size {})",
|
||||
i,
|
||||
total_len / create_len,
|
||||
ser_size,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const NUM_FUNDING_KEYS: usize = 10;
|
||||
let funding_keys: Vec<_> = (0..NUM_FUNDING_KEYS).map(|_| Keypair::new()).collect();
|
||||
let pubkey_amounts: Vec<_> = funding_keys
|
||||
.iter()
|
||||
.map(|key| (key.pubkey(), total / NUM_FUNDING_KEYS as u64))
|
||||
.collect();
|
||||
let instructions = system_instruction::transfer_many(&funding_key.pubkey(), &pubkey_amounts);
|
||||
let message = Message::new(&instructions, Some(&funding_key.pubkey()));
|
||||
let tx = Transaction::new(&[funding_key], message, blockhash);
|
||||
client
|
||||
.send_and_confirm_message(&[funding_key], tx.message)
|
||||
.unwrap();
|
||||
let mut balance = 0;
|
||||
for _ in 0..20 {
|
||||
if let Ok(balance_) = client
|
||||
.get_balance_with_commitment(&funding_keys[0].pubkey(), CommitmentConfig::recent())
|
||||
{
|
||||
if balance_ > 0 {
|
||||
balance = balance_;
|
||||
break;
|
||||
}
|
||||
}
|
||||
sleep(Duration::from_millis(100));
|
||||
}
|
||||
assert!(balance > 0);
|
||||
info!(
|
||||
"funded multiple funding accounts with {:?} lanports",
|
||||
balance
|
||||
);
|
||||
|
||||
let libra_funding_keys: Vec<_> = (0..NUM_FUNDING_KEYS).map(|_| Keypair::new()).collect();
|
||||
for (i, key) in libra_funding_keys.iter().enumerate() {
|
||||
let tx = librapay_transaction::create_account(&funding_keys[i], &key, 1, blockhash);
|
||||
client
|
||||
.send_and_confirm_message(&[&funding_keys[i], &key], tx.message)
|
||||
.unwrap();
|
||||
|
||||
let tx = librapay_transaction::transfer(
|
||||
libra_pay_program_id,
|
||||
&libra_genesis_key.pubkey(),
|
||||
&funding_keys[i],
|
||||
&libra_funding_key,
|
||||
&key.pubkey(),
|
||||
total / NUM_FUNDING_KEYS as u64,
|
||||
blockhash,
|
||||
);
|
||||
client
|
||||
.send_and_confirm_message(&[&funding_keys[i], &libra_funding_key], tx.message)
|
||||
.unwrap();
|
||||
|
||||
info!("funded libra funding key {}", i);
|
||||
}
|
||||
|
||||
let keypair_count = keypairs.len();
|
||||
let amount = total / (keypair_count as u64);
|
||||
for (i, keys) in keypairs[..keypair_count]
|
||||
.chunks(NUM_FUNDING_KEYS)
|
||||
.enumerate()
|
||||
{
|
||||
for (j, key) in keys.iter().enumerate() {
|
||||
let tx = librapay_transaction::transfer(
|
||||
libra_pay_program_id,
|
||||
&libra_genesis_key.pubkey(),
|
||||
&funding_keys[j],
|
||||
&libra_funding_keys[j],
|
||||
&key.pubkey(),
|
||||
amount,
|
||||
blockhash,
|
||||
);
|
||||
|
||||
let _sig = client
|
||||
.async_send_transaction(tx.clone())
|
||||
.expect("create_account in generate_and_fund_keypairs");
|
||||
}
|
||||
|
||||
for (j, key) in keys.iter().enumerate() {
|
||||
let mut times = 0;
|
||||
loop {
|
||||
let balance =
|
||||
librapay_transaction::get_libra_balance(client, &key.pubkey()).unwrap();
|
||||
if balance >= amount {
|
||||
break;
|
||||
} else if times > 20 {
|
||||
info!("timed out.. {} key: {} balance: {}", i, j, balance);
|
||||
break;
|
||||
} else {
|
||||
times += 1;
|
||||
sleep(Duration::from_millis(100));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info!(
|
||||
"funded group {} of {}",
|
||||
i + 1,
|
||||
keypairs.len() / NUM_FUNDING_KEYS
|
||||
);
|
||||
blockhash = get_recent_blockhash(client).0;
|
||||
}
|
||||
|
||||
funding_time.stop();
|
||||
info!("done funding keys, took {} ms", funding_time.as_ms());
|
||||
}
|
||||
|
||||
pub fn generate_and_fund_keypairs<T: 'static + Client + Send + Sync>(
|
||||
client: Arc<T>,
|
||||
faucet_addr: Option<SocketAddr>,
|
||||
funding_key: &Keypair,
|
||||
keypair_count: usize,
|
||||
lamports_per_account: u64,
|
||||
use_move: bool,
|
||||
) -> Result<(Vec<Keypair>, Option<LibraKeys>)> {
|
||||
) -> Result<Vec<Keypair>> {
|
||||
info!("Creating {} keypairs...", keypair_count);
|
||||
let (mut keypairs, extra) = generate_keypairs(funding_key, keypair_count as u64);
|
||||
info!("Get lamports...");
|
||||
@@ -1141,12 +885,6 @@ pub fn generate_and_fund_keypairs<T: 'static + Client + Send + Sync>(
|
||||
let last_key = keypairs[keypair_count - 1].pubkey();
|
||||
let last_keypair_balance = client.get_balance(&last_key).unwrap_or(0);
|
||||
|
||||
#[cfg(feature = "move")]
|
||||
let mut move_keypairs_ret = None;
|
||||
|
||||
#[cfg(not(feature = "move"))]
|
||||
let move_keypairs_ret = None;
|
||||
|
||||
// Repeated runs will eat up keypair balances from transaction fees. In order to quickly
|
||||
// start another bench-tps run without re-funding all of the keypairs, check if the
|
||||
// keypairs still have at least 80% of the expected funds. That should be enough to
|
||||
@@ -1157,10 +895,7 @@ pub fn generate_and_fund_keypairs<T: 'static + Client + Send + Sync>(
|
||||
let max_fee = fee_rate_governor.max_lamports_per_signature;
|
||||
let extra_fees = extra * max_fee;
|
||||
let total_keypairs = keypairs.len() as u64 + 1; // Add one for funding keypair
|
||||
let mut total = lamports_per_account * total_keypairs + extra_fees;
|
||||
if use_move {
|
||||
total *= 3;
|
||||
}
|
||||
let total = lamports_per_account * total_keypairs + extra_fees;
|
||||
|
||||
let funding_key_balance = client.get_balance(&funding_key.pubkey()).unwrap_or(0);
|
||||
info!(
|
||||
@@ -1172,40 +907,6 @@ pub fn generate_and_fund_keypairs<T: 'static + Client + Send + Sync>(
|
||||
airdrop_lamports(client.as_ref(), &faucet_addr.unwrap(), funding_key, total)?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "move")]
|
||||
{
|
||||
if use_move {
|
||||
let libra_genesis_keypair =
|
||||
create_genesis(&funding_key, client.as_ref(), 10_000_000);
|
||||
let libra_mint_program_id = upload_mint_script(&funding_key, client.as_ref());
|
||||
let libra_pay_program_id = upload_payment_script(&funding_key, client.as_ref());
|
||||
|
||||
// Generate another set of keypairs for move accounts.
|
||||
// Still fund the solana ones which will be used for fees.
|
||||
let seed = [0u8; 32];
|
||||
let mut rnd = GenKeys::new(seed);
|
||||
let move_keypairs = rnd.gen_n_keypairs(keypair_count as u64);
|
||||
fund_move_keys(
|
||||
client.as_ref(),
|
||||
funding_key,
|
||||
&move_keypairs,
|
||||
total / 3,
|
||||
&libra_pay_program_id,
|
||||
&libra_mint_program_id,
|
||||
&libra_genesis_keypair,
|
||||
);
|
||||
move_keypairs_ret = Some((
|
||||
libra_genesis_keypair,
|
||||
libra_pay_program_id,
|
||||
libra_mint_program_id,
|
||||
move_keypairs,
|
||||
));
|
||||
|
||||
// Give solana keys 1/3 and move keys 1/3 the lamports. Keep 1/3 for fees.
|
||||
total /= 3;
|
||||
}
|
||||
}
|
||||
|
||||
fund_keys(
|
||||
client,
|
||||
funding_key,
|
||||
@@ -1219,7 +920,7 @@ pub fn generate_and_fund_keypairs<T: 'static + Client + Send + Sync>(
|
||||
// 'generate_keypairs' generates extra keys to be able to have size-aligned funding batches for fund_keys.
|
||||
keypairs.truncate(keypair_count);
|
||||
|
||||
Ok((keypairs, move_keypairs_ret))
|
||||
Ok(keypairs)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -1243,11 +944,11 @@ mod tests {
|
||||
config.duration = Duration::from_secs(5);
|
||||
|
||||
let keypair_count = config.tx_count * config.keypair_multiplier;
|
||||
let (keypairs, _move_keypairs) =
|
||||
generate_and_fund_keypairs(client.clone(), None, &config.id, keypair_count, 20, false)
|
||||
let keypairs =
|
||||
generate_and_fund_keypairs(client.clone(), None, &config.id, keypair_count, 20)
|
||||
.unwrap();
|
||||
|
||||
do_bench_tps(client, config, keypairs, None);
|
||||
do_bench_tps(client, config, keypairs);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1258,9 +959,8 @@ mod tests {
|
||||
let keypair_count = 20;
|
||||
let lamports = 20;
|
||||
|
||||
let (keypairs, _move_keypairs) =
|
||||
generate_and_fund_keypairs(client.clone(), None, &id, keypair_count, lamports, false)
|
||||
.unwrap();
|
||||
let keypairs =
|
||||
generate_and_fund_keypairs(client.clone(), None, &id, keypair_count, lamports).unwrap();
|
||||
|
||||
for kp in &keypairs {
|
||||
assert_eq!(
|
||||
@@ -1282,9 +982,8 @@ mod tests {
|
||||
let keypair_count = 20;
|
||||
let lamports = 20;
|
||||
|
||||
let (keypairs, _move_keypairs) =
|
||||
generate_and_fund_keypairs(client.clone(), None, &id, keypair_count, lamports, false)
|
||||
.unwrap();
|
||||
let keypairs =
|
||||
generate_and_fund_keypairs(client.clone(), None, &id, keypair_count, lamports).unwrap();
|
||||
|
||||
for kp in &keypairs {
|
||||
assert_eq!(client.get_balance(&kp.pubkey()).unwrap(), lamports);
|
||||
|
@@ -23,7 +23,6 @@ pub struct Config {
|
||||
pub read_from_client_file: bool,
|
||||
pub target_lamports_per_signature: u64,
|
||||
pub multi_client: bool,
|
||||
pub use_move: bool,
|
||||
pub num_lamports_per_account: u64,
|
||||
pub target_slots_per_epoch: u64,
|
||||
}
|
||||
@@ -46,7 +45,6 @@ impl Default for Config {
|
||||
read_from_client_file: false,
|
||||
target_lamports_per_signature: FeeRateGovernor::default().target_lamports_per_signature,
|
||||
multi_client: true,
|
||||
use_move: false,
|
||||
num_lamports_per_account: NUM_LAMPORTS_PER_ACCOUNT_DEFAULT,
|
||||
target_slots_per_epoch: 0,
|
||||
}
|
||||
@@ -109,11 +107,6 @@ pub fn build_args<'a, 'b>(version: &'b str) -> App<'a, 'b> {
|
||||
.long("sustained")
|
||||
.help("Use sustained performance mode vs. peak mode. This overlaps the tx generation with transfers."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("use-move")
|
||||
.long("use-move")
|
||||
.help("Use Move language transactions to perform transfers."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("no-multi-client")
|
||||
.long("no-multi-client")
|
||||
@@ -263,7 +256,6 @@ pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config {
|
||||
args.target_lamports_per_signature = v.to_string().parse().expect("can't parse lamports");
|
||||
}
|
||||
|
||||
args.use_move = matches.is_present("use-move");
|
||||
args.multi_client = !matches.is_present("no-multi-client");
|
||||
|
||||
if let Some(v) = matches.value_of("num_lamports_per_account") {
|
||||
|
@@ -29,7 +29,6 @@ fn main() {
|
||||
write_to_client_file,
|
||||
read_from_client_file,
|
||||
target_lamports_per_signature,
|
||||
use_move,
|
||||
multi_client,
|
||||
num_lamports_per_account,
|
||||
..
|
||||
@@ -86,7 +85,7 @@ fn main() {
|
||||
Arc::new(get_client(&nodes))
|
||||
};
|
||||
|
||||
let (keypairs, move_keypairs) = if *read_from_client_file && !use_move {
|
||||
let keypairs = if *read_from_client_file {
|
||||
let path = Path::new(&client_ids_and_stake_file);
|
||||
let file = File::open(path).unwrap();
|
||||
|
||||
@@ -115,8 +114,8 @@ fn main() {
|
||||
// Sort keypairs so that do_bench_tps() uses the same subset of accounts for each run.
|
||||
// This prevents the amount of storage needed for bench-tps accounts from creeping up
|
||||
// across multiple runs.
|
||||
keypairs.sort_by(|x, y| x.pubkey().to_string().cmp(&y.pubkey().to_string()));
|
||||
(keypairs, None)
|
||||
keypairs.sort_by_key(|x| x.pubkey().to_string());
|
||||
keypairs
|
||||
} else {
|
||||
generate_and_fund_keypairs(
|
||||
client.clone(),
|
||||
@@ -124,7 +123,6 @@ fn main() {
|
||||
&id,
|
||||
keypair_count,
|
||||
*num_lamports_per_account,
|
||||
*use_move,
|
||||
)
|
||||
.unwrap_or_else(|e| {
|
||||
eprintln!("Error could not fund keys: {:?}", e);
|
||||
@@ -132,5 +130,5 @@ fn main() {
|
||||
})
|
||||
};
|
||||
|
||||
do_bench_tps(client, cli_config, keypairs, move_keypairs);
|
||||
do_bench_tps(client, cli_config, keypairs);
|
||||
}
|
||||
|
@@ -6,17 +6,11 @@ use solana_core::cluster_info::VALIDATOR_PORT_RANGE;
|
||||
use solana_core::validator::ValidatorConfig;
|
||||
use solana_faucet::faucet::run_local_faucet;
|
||||
use solana_local_cluster::local_cluster::{ClusterConfig, LocalCluster};
|
||||
#[cfg(feature = "move")]
|
||||
use solana_sdk::move_loader::solana_move_loader_program;
|
||||
use solana_sdk::signature::{Keypair, Signer};
|
||||
use std::sync::{mpsc::channel, Arc};
|
||||
use std::time::Duration;
|
||||
|
||||
fn test_bench_tps_local_cluster(config: Config) {
|
||||
#[cfg(feature = "move")]
|
||||
let native_instruction_processors = vec![solana_move_loader_program()];
|
||||
|
||||
#[cfg(not(feature = "move"))]
|
||||
let native_instruction_processors = vec![];
|
||||
|
||||
solana_logger::setup();
|
||||
@@ -48,17 +42,16 @@ fn test_bench_tps_local_cluster(config: Config) {
|
||||
let lamports_per_account = 100;
|
||||
|
||||
let keypair_count = config.tx_count * config.keypair_multiplier;
|
||||
let (keypairs, move_keypairs) = generate_and_fund_keypairs(
|
||||
let keypairs = generate_and_fund_keypairs(
|
||||
client.clone(),
|
||||
Some(faucet_addr),
|
||||
&config.id,
|
||||
keypair_count,
|
||||
lamports_per_account,
|
||||
config.use_move,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let _total = do_bench_tps(client, config, keypairs, move_keypairs);
|
||||
let _total = do_bench_tps(client, config, keypairs);
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
assert!(_total > 100);
|
||||
@@ -73,14 +66,3 @@ fn test_bench_tps_local_cluster_solana() {
|
||||
|
||||
test_bench_tps_local_cluster(config);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_bench_tps_local_cluster_move() {
|
||||
let mut config = Config::default();
|
||||
config.tx_count = 100;
|
||||
config.duration = Duration::from_secs(10);
|
||||
config.use_move = true;
|
||||
|
||||
test_bench_tps_local_cluster(config);
|
||||
}
|
||||
|
@@ -16,6 +16,3 @@ steps:
|
||||
timeout_in_minutes: 240
|
||||
name: "publish crate"
|
||||
branches: "!master"
|
||||
# - command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_stable_docker_image ci/test-move.sh"
|
||||
# name: "move"
|
||||
# timeout_in_minutes: 20
|
||||
|
@@ -45,7 +45,7 @@ linux)
|
||||
TARGET=x86_64-unknown-linux-gnu
|
||||
;;
|
||||
windows)
|
||||
TARGET=x86_64-pc-windows-gnu
|
||||
TARGET=x86_64-pc-windows-msvc
|
||||
;;
|
||||
*)
|
||||
echo CI_OS_NAME unset
|
||||
|
@@ -7,7 +7,7 @@ source multinode-demo/common.sh
|
||||
|
||||
rm -rf config/run/init-completed config/ledger config/snapshot-ledger
|
||||
|
||||
timeout 15 ./run.sh &
|
||||
timeout 120 ./run.sh &
|
||||
pid=$!
|
||||
|
||||
attempts=20
|
||||
|
@@ -27,5 +27,5 @@ Alternatively, you can source it from within a script:
|
||||
local PATCH=0
|
||||
local SPECIAL=""
|
||||
|
||||
semverParseInto "1.2.12" MAJOR MINOR PATCH SPECIAL
|
||||
semverParseInto "1.2.21" MAJOR MINOR PATCH SPECIAL
|
||||
semverParseInto "3.2.1" MAJOR MINOR PATCH SPECIAL
|
||||
|
@@ -1 +0,0 @@
|
||||
test-stable.sh
|
@@ -47,7 +47,6 @@ echo "Executing $testName"
|
||||
case $testName in
|
||||
test-stable)
|
||||
_ cargo +"$rust_stable" test --jobs "$NPROC" --all --exclude solana-local-cluster ${V:+--verbose} -- --nocapture
|
||||
_ cargo +"$rust_stable" test --manifest-path bench-tps/Cargo.toml --features=move ${V:+--verbose} test_bench_tps_local_cluster_move -- --nocapture
|
||||
;;
|
||||
test-stable-perf)
|
||||
ci/affects-files.sh \
|
||||
@@ -93,27 +92,6 @@ test-stable-perf)
|
||||
_ cargo +"$rust_stable" build --bins ${V:+--verbose}
|
||||
_ cargo +"$rust_stable" test --package solana-perf --package solana-ledger --package solana-core --lib ${V:+--verbose} -- --nocapture
|
||||
;;
|
||||
test-move)
|
||||
ci/affects-files.sh \
|
||||
Cargo.lock$ \
|
||||
Cargo.toml$ \
|
||||
^ci/rust-version.sh \
|
||||
^ci/test-stable.sh \
|
||||
^ci/test-move.sh \
|
||||
^programs/move_loader \
|
||||
^programs/librapay \
|
||||
^logger/ \
|
||||
^runtime/ \
|
||||
^sdk/ \
|
||||
|| {
|
||||
annotate --style info \
|
||||
"Skipped $testName as no relevant files were modified"
|
||||
exit 0
|
||||
}
|
||||
_ cargo +"$rust_stable" test --manifest-path programs/move_loader/Cargo.toml ${V:+--verbose} -- --nocapture
|
||||
_ cargo +"$rust_stable" test --manifest-path programs/librapay/Cargo.toml ${V:+--verbose} -- --nocapture
|
||||
exit 0
|
||||
;;
|
||||
test-local-cluster)
|
||||
_ cargo +"$rust_stable" build --release --bins ${V:+--verbose}
|
||||
_ cargo +"$rust_stable" test --release --package solana-local-cluster ${V:+--verbose} -- --nocapture --test-threads=1
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-clap-utils"
|
||||
version = "1.2.12"
|
||||
version = "1.2.21"
|
||||
description = "Solana utilities for the clap"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@@ -11,8 +11,8 @@ edition = "2018"
|
||||
[dependencies]
|
||||
clap = "2.33.0"
|
||||
rpassword = "4.0"
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "1.2.12" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.12" }
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "1.2.21" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.21" }
|
||||
thiserror = "1.0.11"
|
||||
tiny-bip39 = "0.7.0"
|
||||
url = "2.1.0"
|
||||
|
@@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-cli-config"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.2.12"
|
||||
version = "1.2.21"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
|
@@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-cli"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.2.12"
|
||||
version = "1.2.21"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@@ -27,29 +27,29 @@ reqwest = { version = "0.10.4", default-features = false, features = ["blocking"
|
||||
serde = "1.0.110"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.53"
|
||||
solana-account-decoder = { path = "../account-decoder", version = "1.2.12" }
|
||||
solana-budget-program = { path = "../programs/budget", version = "1.2.12" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.12" }
|
||||
solana-cli-config = { path = "../cli-config", version = "1.2.12" }
|
||||
solana-client = { path = "../client", version = "1.2.12" }
|
||||
solana-config-program = { path = "../programs/config", version = "1.2.12" }
|
||||
solana-faucet = { path = "../faucet", version = "1.2.12" }
|
||||
solana-logger = { path = "../logger", version = "1.2.12" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.2.12" }
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "1.2.12" }
|
||||
solana-runtime = { path = "../runtime", version = "1.2.12" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.12" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.2.12" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "1.2.12" }
|
||||
solana-version = { path = "../version", version = "1.2.12" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.2.12" }
|
||||
solana-vote-signer = { path = "../vote-signer", version = "1.2.12" }
|
||||
solana-account-decoder = { path = "../account-decoder", version = "1.2.21" }
|
||||
solana-budget-program = { path = "../programs/budget", version = "1.2.21" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.21" }
|
||||
solana-cli-config = { path = "../cli-config", version = "1.2.21" }
|
||||
solana-client = { path = "../client", version = "1.2.21" }
|
||||
solana-config-program = { path = "../programs/config", version = "1.2.21" }
|
||||
solana-faucet = { path = "../faucet", version = "1.2.21" }
|
||||
solana-logger = { path = "../logger", version = "1.2.21" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.2.21" }
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "1.2.21" }
|
||||
solana-runtime = { path = "../runtime", version = "1.2.21" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.21" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.2.21" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "1.2.21" }
|
||||
solana-version = { path = "../version", version = "1.2.21" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.2.21" }
|
||||
solana-vote-signer = { path = "../vote-signer", version = "1.2.21" }
|
||||
thiserror = "1.0.19"
|
||||
url = "2.1.1"
|
||||
|
||||
[dev-dependencies]
|
||||
solana-core = { path = "../core", version = "1.2.12" }
|
||||
solana-budget-program = { path = "../programs/budget", version = "1.2.12" }
|
||||
solana-core = { path = "../core", version = "1.2.21" }
|
||||
solana-budget-program = { path = "../programs/budget", version = "1.2.21" }
|
||||
tempfile = "3.1.0"
|
||||
|
||||
[[bin]]
|
||||
|
@@ -246,8 +246,8 @@ pub enum CliCommand {
|
||||
},
|
||||
TransactionHistory {
|
||||
address: Pubkey,
|
||||
end_slot: Option<Slot>, // None == latest slot
|
||||
slot_limit: Option<u64>, // None == search full history
|
||||
before: Option<Signature>,
|
||||
limit: usize,
|
||||
},
|
||||
// Nonce commands
|
||||
AuthorizeNonceAccount {
|
||||
@@ -1230,7 +1230,7 @@ fn process_show_account(
|
||||
let cli_account = CliAccount {
|
||||
keyed_account: RpcKeyedAccount {
|
||||
pubkey: account_pubkey.to_string(),
|
||||
account: UiAccount::encode(account, UiAccountEncoding::Binary),
|
||||
account: UiAccount::encode(account_pubkey, account, UiAccountEncoding::Binary, None),
|
||||
},
|
||||
use_lamports_unit,
|
||||
};
|
||||
@@ -1837,9 +1837,9 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
|
||||
} => process_show_validators(&rpc_client, config, *use_lamports_unit, *commitment_config),
|
||||
CliCommand::TransactionHistory {
|
||||
address,
|
||||
end_slot,
|
||||
slot_limit,
|
||||
} => process_transaction_history(&rpc_client, address, *end_slot, *slot_limit),
|
||||
before,
|
||||
limit,
|
||||
} => process_transaction_history(&rpc_client, config, address, *before, *limit),
|
||||
|
||||
// Nonce Commands
|
||||
|
||||
|
@@ -16,7 +16,6 @@ use solana_client::{
|
||||
pubsub_client::{PubsubClient, SlotInfoMessage},
|
||||
rpc_client::RpcClient,
|
||||
rpc_config::{RpcLargestAccountsConfig, RpcLargestAccountsFilter},
|
||||
rpc_request::MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS_SLOT_RANGE,
|
||||
};
|
||||
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
|
||||
use solana_sdk::{
|
||||
@@ -27,6 +26,7 @@ use solana_sdk::{
|
||||
message::Message,
|
||||
native_token::lamports_to_sol,
|
||||
pubkey::{self, Pubkey},
|
||||
signature::Signature,
|
||||
system_instruction, system_program,
|
||||
sysvar::{
|
||||
self,
|
||||
@@ -256,9 +256,8 @@ impl ClusterQuerySubCommands for App<'_, '_> {
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("transaction-history")
|
||||
.about("Show historical transactions affecting the given address, \
|
||||
ordered based on the slot in which they were confirmed in \
|
||||
from lowest to highest slot")
|
||||
.about("Show historical transactions affecting the given address \
|
||||
from newest to oldest")
|
||||
.arg(
|
||||
pubkey!(Arg::with_name("address")
|
||||
.index(1)
|
||||
@@ -266,26 +265,22 @@ impl ClusterQuerySubCommands for App<'_, '_> {
|
||||
.required(true),
|
||||
"Account address"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("end_slot")
|
||||
.takes_value(false)
|
||||
.value_name("SLOT")
|
||||
.index(2)
|
||||
.validator(is_slot)
|
||||
.help(
|
||||
"Slot to start from [default: latest slot at maximum commitment]"
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("limit")
|
||||
.long("limit")
|
||||
.takes_value(true)
|
||||
.value_name("NUMBER OF SLOTS")
|
||||
.value_name("LIMIT")
|
||||
.validator(is_slot)
|
||||
.help(
|
||||
"Limit the search to this many slots"
|
||||
),
|
||||
),
|
||||
.default_value("1000")
|
||||
.help("Maximum number of transaction signatures to return"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("before")
|
||||
.long("before")
|
||||
.value_name("TRANSACTION_SIGNATURE")
|
||||
.takes_value(true)
|
||||
.help("Start with the first signature older than this one"),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -453,14 +448,22 @@ pub fn parse_transaction_history(
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let address = pubkey_of_signer(matches, "address", wallet_manager)?.unwrap();
|
||||
let end_slot = value_t!(matches, "end_slot", Slot).ok();
|
||||
let slot_limit = value_t!(matches, "limit", u64).ok();
|
||||
|
||||
let before = match matches.value_of("before") {
|
||||
Some(signature) => Some(
|
||||
signature
|
||||
.parse()
|
||||
.map_err(|err| CliError::BadParameter(format!("Invalid signature: {}", err)))?,
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
let limit = value_t_or_exit!(matches, "limit", usize);
|
||||
|
||||
Ok(CliCommandInfo {
|
||||
command: CliCommand::TransactionHistory {
|
||||
address,
|
||||
end_slot,
|
||||
slot_limit,
|
||||
before,
|
||||
limit,
|
||||
},
|
||||
signers: vec![],
|
||||
})
|
||||
@@ -1276,41 +1279,36 @@ pub fn process_show_validators(
|
||||
|
||||
pub fn process_transaction_history(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
address: &Pubkey,
|
||||
end_slot: Option<Slot>, // None == use latest slot
|
||||
slot_limit: Option<u64>,
|
||||
before: Option<Signature>,
|
||||
limit: usize,
|
||||
) -> ProcessResult {
|
||||
let end_slot = {
|
||||
if let Some(end_slot) = end_slot {
|
||||
end_slot
|
||||
let results = rpc_client.get_confirmed_signatures_for_address2_with_config(
|
||||
address,
|
||||
before,
|
||||
Some(limit),
|
||||
)?;
|
||||
|
||||
let transactions_found = format!("{} transactions found", results.len());
|
||||
|
||||
for result in results {
|
||||
if config.verbose {
|
||||
println!(
|
||||
"{} [slot={} status={}] {}",
|
||||
result.signature,
|
||||
result.slot,
|
||||
match result.err {
|
||||
None => "Confirmed".to_string(),
|
||||
Some(err) => format!("Failed: {:?}", err),
|
||||
},
|
||||
result.memo.unwrap_or_else(|| "".to_string()),
|
||||
);
|
||||
} else {
|
||||
rpc_client.get_slot_with_commitment(CommitmentConfig::max())?
|
||||
println!("{}", result.signature);
|
||||
}
|
||||
};
|
||||
let mut start_slot = match slot_limit {
|
||||
Some(slot_limit) => end_slot.saturating_sub(slot_limit),
|
||||
None => rpc_client.minimum_ledger_slot()?,
|
||||
};
|
||||
|
||||
println!(
|
||||
"Transactions affecting {} within slots [{},{}]",
|
||||
address, start_slot, end_slot
|
||||
);
|
||||
|
||||
let mut transaction_count = 0;
|
||||
while start_slot < end_slot {
|
||||
let signatures = rpc_client.get_confirmed_signatures_for_address(
|
||||
address,
|
||||
start_slot,
|
||||
(start_slot + MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS_SLOT_RANGE).min(end_slot),
|
||||
)?;
|
||||
for signature in &signatures {
|
||||
println!("{}", signature);
|
||||
}
|
||||
transaction_count += signatures.len();
|
||||
start_slot += MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS_SLOT_RANGE;
|
||||
}
|
||||
Ok(format!("{} transactions found", transaction_count))
|
||||
Ok(transactions_found)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@@ -345,7 +345,12 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
let nonce_pubkey = Pubkey::new(&[4u8; 32]);
|
||||
let rpc_nonce_account = UiAccount::encode(nonce_account, UiAccountEncoding::Binary);
|
||||
let rpc_nonce_account = UiAccount::encode(
|
||||
&nonce_pubkey,
|
||||
nonce_account,
|
||||
UiAccountEncoding::Binary64,
|
||||
None,
|
||||
);
|
||||
let get_account_response = json!(Response {
|
||||
context: RpcResponseContext { slot: 1 },
|
||||
value: json!(Some(rpc_nonce_account)),
|
||||
|
@@ -6,9 +6,10 @@ use crate::{
|
||||
use bincode::deserialize;
|
||||
use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
|
||||
use reqwest::blocking::Client;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
use solana_account_decoder::validator_info::{
|
||||
self, ValidatorInfo, MAX_LONG_FIELD_LENGTH, MAX_SHORT_FIELD_LENGTH,
|
||||
};
|
||||
use solana_clap_utils::{
|
||||
input_parsers::pubkey_of,
|
||||
input_validators::{is_pubkey, is_url},
|
||||
@@ -27,23 +28,6 @@ use solana_sdk::{
|
||||
};
|
||||
use std::{error, sync::Arc};
|
||||
|
||||
pub const MAX_SHORT_FIELD_LENGTH: usize = 70;
|
||||
pub const MAX_LONG_FIELD_LENGTH: usize = 300;
|
||||
pub const MAX_VALIDATOR_INFO: u64 = 576;
|
||||
|
||||
solana_sdk::declare_id!("Va1idator1nfo111111111111111111111111111111");
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize, Default)]
|
||||
pub struct ValidatorInfo {
|
||||
info: String,
|
||||
}
|
||||
|
||||
impl ConfigState for ValidatorInfo {
|
||||
fn max_space() -> u64 {
|
||||
MAX_VALIDATOR_INFO
|
||||
}
|
||||
}
|
||||
|
||||
// Return an error if a validator details are longer than the max length.
|
||||
pub fn check_details_length(string: String) -> Result<(), String> {
|
||||
if string.len() > MAX_LONG_FIELD_LENGTH {
|
||||
@@ -289,7 +273,7 @@ pub fn process_set_validator_info(
|
||||
.iter()
|
||||
.filter(|(_, account)| {
|
||||
let key_list: ConfigKeys = deserialize(&account.data).map_err(|_| false).unwrap();
|
||||
key_list.keys.contains(&(id(), false))
|
||||
key_list.keys.contains(&(validator_info::id(), false))
|
||||
})
|
||||
.find(|(pubkey, account)| {
|
||||
let (validator_pubkey, _) = parse_validator_info(&pubkey, &account).unwrap();
|
||||
@@ -328,7 +312,10 @@ pub fn process_set_validator_info(
|
||||
};
|
||||
|
||||
let build_message = |lamports| {
|
||||
let keys = vec![(id(), false), (config.signers[0].pubkey(), true)];
|
||||
let keys = vec![
|
||||
(validator_info::id(), false),
|
||||
(config.signers[0].pubkey(), true),
|
||||
];
|
||||
if balance == 0 {
|
||||
println!(
|
||||
"Publishing info for Validator {:?}",
|
||||
@@ -400,7 +387,7 @@ pub fn process_get_validator_info(
|
||||
let key_list: ConfigKeys = deserialize(&validator_info_account.data)
|
||||
.map_err(|_| false)
|
||||
.unwrap();
|
||||
key_list.keys.contains(&(id(), false))
|
||||
key_list.keys.contains(&(validator_info::id(), false))
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
@@ -502,7 +489,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_parse_validator_info() {
|
||||
let pubkey = Pubkey::new_rand();
|
||||
let keys = vec![(id(), false), (pubkey, true)];
|
||||
let keys = vec![(validator_info::id(), false), (pubkey, true)];
|
||||
let config = ConfigKeys { keys };
|
||||
|
||||
let mut info = Map::new();
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-client"
|
||||
version = "1.2.12"
|
||||
version = "1.2.21"
|
||||
description = "Solana Client"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@@ -19,11 +19,11 @@ reqwest = { version = "0.10.4", default-features = false, features = ["blocking"
|
||||
serde = "1.0.110"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.53"
|
||||
solana-account-decoder = { path = "../account-decoder", version = "1.2.12" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.2.12" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.12" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "1.2.12" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.2.12" }
|
||||
solana-account-decoder = { path = "../account-decoder", version = "1.2.21" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.2.21" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.21" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "1.2.21" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.2.21" }
|
||||
thiserror = "1.0"
|
||||
tungstenite = "0.10.1"
|
||||
url = "2.1.1"
|
||||
@@ -32,7 +32,7 @@ url = "2.1.1"
|
||||
assert_matches = "1.3.0"
|
||||
jsonrpc-core = "14.1.0"
|
||||
jsonrpc-http-server = "14.1.0"
|
||||
solana-logger = { path = "../logger", version = "1.2.12" }
|
||||
solana-logger = { path = "../logger", version = "1.2.21" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@@ -2,8 +2,12 @@ use crate::{
|
||||
client_error::{ClientError, ClientErrorKind, Result as ClientResult},
|
||||
http_sender::HttpSender,
|
||||
mock_sender::{MockSender, Mocks},
|
||||
rpc_config::{RpcLargestAccountsConfig, RpcSendTransactionConfig},
|
||||
rpc_request::{RpcError, RpcRequest},
|
||||
rpc_config::RpcAccountInfoConfig,
|
||||
rpc_config::{
|
||||
RpcGetConfirmedSignaturesForAddress2Config, RpcLargestAccountsConfig,
|
||||
RpcSendTransactionConfig, RpcTokenAccountsFilter,
|
||||
},
|
||||
rpc_request::{RpcError, RpcRequest, TokenAccountsFilter},
|
||||
rpc_response::*,
|
||||
rpc_sender::RpcSender,
|
||||
};
|
||||
@@ -11,7 +15,15 @@ use bincode::serialize;
|
||||
use indicatif::{ProgressBar, ProgressStyle};
|
||||
use log::*;
|
||||
use serde_json::{json, Value};
|
||||
use solana_account_decoder::UiAccount;
|
||||
use solana_account_decoder::{
|
||||
parse_token::{
|
||||
get_token_account_mint, parse_token, TokenAccountType, UiMint, UiMultisig, UiTokenAccount,
|
||||
UiTokenAmount,
|
||||
},
|
||||
UiAccount,
|
||||
UiAccountData::{Binary, Binary64},
|
||||
UiAccountEncoding,
|
||||
};
|
||||
use solana_sdk::{
|
||||
account::Account,
|
||||
clock::{
|
||||
@@ -33,6 +45,7 @@ use solana_transaction_status::{
|
||||
};
|
||||
use solana_vote_program::vote_state::MAX_LOCKOUT_HISTORY;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
net::SocketAddr,
|
||||
thread::sleep,
|
||||
time::{Duration, Instant},
|
||||
@@ -283,6 +296,32 @@ impl RpcClient {
|
||||
Ok(signatures)
|
||||
}
|
||||
|
||||
pub fn get_confirmed_signatures_for_address2(
|
||||
&self,
|
||||
address: &Pubkey,
|
||||
) -> ClientResult<Vec<RpcConfirmedTransactionStatusWithSignature>> {
|
||||
self.get_confirmed_signatures_for_address2_with_config(address, None, None)
|
||||
}
|
||||
|
||||
pub fn get_confirmed_signatures_for_address2_with_config(
|
||||
&self,
|
||||
address: &Pubkey,
|
||||
before: Option<Signature>,
|
||||
limit: Option<usize>,
|
||||
) -> ClientResult<Vec<RpcConfirmedTransactionStatusWithSignature>> {
|
||||
let config = RpcGetConfirmedSignaturesForAddress2Config {
|
||||
before: before.map(|signature| signature.to_string()),
|
||||
limit,
|
||||
};
|
||||
|
||||
let result: Vec<RpcConfirmedTransactionStatusWithSignature> = self.send(
|
||||
RpcRequest::GetConfirmedSignaturesForAddress2,
|
||||
json!([address.to_string(), config]),
|
||||
)?;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn get_confirmed_transaction(
|
||||
&self,
|
||||
signature: &Signature,
|
||||
@@ -438,9 +477,13 @@ impl RpcClient {
|
||||
pubkey: &Pubkey,
|
||||
commitment_config: CommitmentConfig,
|
||||
) -> RpcResult<Option<Account>> {
|
||||
let config = RpcAccountInfoConfig {
|
||||
encoding: Some(UiAccountEncoding::Binary64),
|
||||
commitment: Some(commitment_config),
|
||||
};
|
||||
let response = self.sender.send(
|
||||
RpcRequest::GetAccountInfo,
|
||||
json!([pubkey.to_string(), commitment_config]),
|
||||
json!([pubkey.to_string(), config]),
|
||||
);
|
||||
|
||||
response
|
||||
@@ -452,8 +495,17 @@ impl RpcClient {
|
||||
}
|
||||
let Response {
|
||||
context,
|
||||
value: rpc_account,
|
||||
value: mut rpc_account,
|
||||
} = serde_json::from_value::<Response<Option<UiAccount>>>(result_json)?;
|
||||
if let Some(ref mut account) = rpc_account {
|
||||
if let Binary(_) = &account.data {
|
||||
let tmp = Binary64(String::new());
|
||||
match std::mem::replace(&mut account.data, tmp) {
|
||||
Binary(new_data) => account.data = Binary64(new_data),
|
||||
_ => panic!("should have gotten binary here."),
|
||||
}
|
||||
}
|
||||
}
|
||||
trace!("Response account {:?} {:?}", pubkey, rpc_account);
|
||||
let account = rpc_account.and_then(|rpc_account| rpc_account.decode());
|
||||
Ok(Response {
|
||||
@@ -511,17 +563,7 @@ impl RpcClient {
|
||||
pub fn get_program_accounts(&self, pubkey: &Pubkey) -> ClientResult<Vec<(Pubkey, Account)>> {
|
||||
let accounts: Vec<RpcKeyedAccount> =
|
||||
self.send(RpcRequest::GetProgramAccounts, json!([pubkey.to_string()]))?;
|
||||
let mut pubkey_accounts: Vec<(Pubkey, Account)> = Vec::new();
|
||||
for RpcKeyedAccount { pubkey, account } in accounts.into_iter() {
|
||||
let pubkey = pubkey.parse().map_err(|_| {
|
||||
ClientError::new_with_request(
|
||||
RpcError::ParseError("Pubkey".to_string()).into(),
|
||||
RpcRequest::GetProgramAccounts,
|
||||
)
|
||||
})?;
|
||||
pubkey_accounts.push((pubkey, account.decode().unwrap()));
|
||||
}
|
||||
Ok(pubkey_accounts)
|
||||
parse_keyed_accounts(accounts, RpcRequest::GetProgramAccounts)
|
||||
}
|
||||
|
||||
/// Request the transaction count.
|
||||
@@ -668,6 +710,256 @@ impl RpcClient {
|
||||
Ok(hash)
|
||||
}
|
||||
|
||||
pub fn get_token_account(&self, pubkey: &Pubkey) -> ClientResult<Option<UiTokenAccount>> {
|
||||
Ok(self
|
||||
.get_token_account_with_commitment(pubkey, CommitmentConfig::default())?
|
||||
.value)
|
||||
}
|
||||
|
||||
pub fn get_token_account_with_commitment(
|
||||
&self,
|
||||
pubkey: &Pubkey,
|
||||
commitment_config: CommitmentConfig,
|
||||
) -> RpcResult<Option<UiTokenAccount>> {
|
||||
let Response {
|
||||
context,
|
||||
value: account,
|
||||
} = self.get_account_with_commitment(pubkey, commitment_config)?;
|
||||
|
||||
if account.is_none() {
|
||||
return Err(RpcError::ForUser(format!("AccountNotFound: pubkey={}", pubkey)).into());
|
||||
}
|
||||
let account = account.unwrap();
|
||||
let mint = get_token_account_mint(&account.data)
|
||||
.and_then(|mint_pubkey| {
|
||||
self.get_token_mint_with_commitment(&mint_pubkey, commitment_config)
|
||||
.ok()
|
||||
.map(|response| response.value)
|
||||
.flatten()
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
Into::<ClientError>::into(RpcError::ForUser(format!(
|
||||
"AccountNotFound: mint for token acccount pubkey={}",
|
||||
pubkey
|
||||
)))
|
||||
})?;
|
||||
|
||||
Ok(Response {
|
||||
context,
|
||||
value: match parse_token(&account.data, Some(mint.decimals)) {
|
||||
Ok(TokenAccountType::Account(ui_token_account)) => Some(ui_token_account),
|
||||
_ => None,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_token_mint(&self, pubkey: &Pubkey) -> ClientResult<Option<UiMint>> {
|
||||
Ok(self
|
||||
.get_token_mint_with_commitment(pubkey, CommitmentConfig::default())?
|
||||
.value)
|
||||
}
|
||||
|
||||
pub fn get_token_mint_with_commitment(
|
||||
&self,
|
||||
pubkey: &Pubkey,
|
||||
commitment_config: CommitmentConfig,
|
||||
) -> RpcResult<Option<UiMint>> {
|
||||
let Response {
|
||||
context,
|
||||
value: account,
|
||||
} = self.get_account_with_commitment(pubkey, commitment_config)?;
|
||||
|
||||
Ok(Response {
|
||||
context,
|
||||
value: account
|
||||
.map(|account| match parse_token(&account.data, None) {
|
||||
Ok(TokenAccountType::Mint(ui_token_mint)) => Some(ui_token_mint),
|
||||
_ => None,
|
||||
})
|
||||
.flatten(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_token_multisig(&self, pubkey: &Pubkey) -> ClientResult<Option<UiMultisig>> {
|
||||
Ok(self
|
||||
.get_token_multisig_with_commitment(pubkey, CommitmentConfig::default())?
|
||||
.value)
|
||||
}
|
||||
|
||||
pub fn get_token_multisig_with_commitment(
|
||||
&self,
|
||||
pubkey: &Pubkey,
|
||||
commitment_config: CommitmentConfig,
|
||||
) -> RpcResult<Option<UiMultisig>> {
|
||||
let Response {
|
||||
context,
|
||||
value: account,
|
||||
} = self.get_account_with_commitment(pubkey, commitment_config)?;
|
||||
|
||||
Ok(Response {
|
||||
context,
|
||||
value: account
|
||||
.map(|account| match parse_token(&account.data, None) {
|
||||
Ok(TokenAccountType::Multisig(ui_token_multisig)) => Some(ui_token_multisig),
|
||||
_ => None,
|
||||
})
|
||||
.flatten(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_token_account_balance(&self, pubkey: &Pubkey) -> ClientResult<UiTokenAmount> {
|
||||
Ok(self
|
||||
.get_token_account_balance_with_commitment(pubkey, CommitmentConfig::default())?
|
||||
.value)
|
||||
}
|
||||
|
||||
pub fn get_token_account_balance_with_commitment(
|
||||
&self,
|
||||
pubkey: &Pubkey,
|
||||
commitment_config: CommitmentConfig,
|
||||
) -> RpcResult<UiTokenAmount> {
|
||||
self.send(
|
||||
RpcRequest::GetTokenAccountBalance,
|
||||
json!([pubkey.to_string(), commitment_config]),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_token_accounts_by_delegate(
|
||||
&self,
|
||||
delegate: &Pubkey,
|
||||
token_account_filter: TokenAccountsFilter,
|
||||
) -> ClientResult<Vec<(Pubkey, UiTokenAccount)>> {
|
||||
Ok(self
|
||||
.get_token_accounts_by_delegate_with_commitment(
|
||||
delegate,
|
||||
token_account_filter,
|
||||
CommitmentConfig::default(),
|
||||
)?
|
||||
.value)
|
||||
}
|
||||
|
||||
pub fn get_token_accounts_by_delegate_with_commitment(
|
||||
&self,
|
||||
delegate: &Pubkey,
|
||||
token_account_filter: TokenAccountsFilter,
|
||||
commitment_config: CommitmentConfig,
|
||||
) -> RpcResult<Vec<(Pubkey, UiTokenAccount)>> {
|
||||
let token_account_filter = match token_account_filter {
|
||||
TokenAccountsFilter::Mint(mint) => RpcTokenAccountsFilter::Mint(mint.to_string()),
|
||||
TokenAccountsFilter::ProgramId(program_id) => {
|
||||
RpcTokenAccountsFilter::ProgramId(program_id.to_string())
|
||||
}
|
||||
};
|
||||
let Response {
|
||||
context,
|
||||
value: accounts,
|
||||
} = self.send(
|
||||
RpcRequest::GetTokenAccountsByDelegate,
|
||||
json!([
|
||||
delegate.to_string(),
|
||||
token_account_filter,
|
||||
commitment_config
|
||||
]),
|
||||
)?;
|
||||
let pubkey_accounts = self.accounts_to_token_accounts(
|
||||
commitment_config,
|
||||
parse_keyed_accounts(accounts, RpcRequest::GetTokenAccountsByDelegate)?,
|
||||
);
|
||||
Ok(Response {
|
||||
context,
|
||||
value: pubkey_accounts,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_token_accounts_by_owner(
|
||||
&self,
|
||||
owner: &Pubkey,
|
||||
token_account_filter: TokenAccountsFilter,
|
||||
) -> ClientResult<Vec<(Pubkey, UiTokenAccount)>> {
|
||||
Ok(self
|
||||
.get_token_accounts_by_owner_with_commitment(
|
||||
owner,
|
||||
token_account_filter,
|
||||
CommitmentConfig::default(),
|
||||
)?
|
||||
.value)
|
||||
}
|
||||
|
||||
pub fn get_token_accounts_by_owner_with_commitment(
|
||||
&self,
|
||||
owner: &Pubkey,
|
||||
token_account_filter: TokenAccountsFilter,
|
||||
commitment_config: CommitmentConfig,
|
||||
) -> RpcResult<Vec<(Pubkey, UiTokenAccount)>> {
|
||||
let token_account_filter = match token_account_filter {
|
||||
TokenAccountsFilter::Mint(mint) => RpcTokenAccountsFilter::Mint(mint.to_string()),
|
||||
TokenAccountsFilter::ProgramId(program_id) => {
|
||||
RpcTokenAccountsFilter::ProgramId(program_id.to_string())
|
||||
}
|
||||
};
|
||||
let Response {
|
||||
context,
|
||||
value: accounts,
|
||||
} = self.send(
|
||||
RpcRequest::GetTokenAccountsByOwner,
|
||||
json!([owner.to_string(), token_account_filter, commitment_config]),
|
||||
)?;
|
||||
let pubkey_accounts = self.accounts_to_token_accounts(
|
||||
commitment_config,
|
||||
parse_keyed_accounts(accounts, RpcRequest::GetTokenAccountsByDelegate)?,
|
||||
);
|
||||
Ok(Response {
|
||||
context,
|
||||
value: pubkey_accounts,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_token_supply(&self, mint: &Pubkey) -> ClientResult<UiTokenAmount> {
|
||||
Ok(self
|
||||
.get_token_supply_with_commitment(mint, CommitmentConfig::default())?
|
||||
.value)
|
||||
}
|
||||
|
||||
pub fn get_token_supply_with_commitment(
|
||||
&self,
|
||||
mint: &Pubkey,
|
||||
commitment_config: CommitmentConfig,
|
||||
) -> RpcResult<UiTokenAmount> {
|
||||
self.send(
|
||||
RpcRequest::GetTokenSupply,
|
||||
json!([mint.to_string(), commitment_config]),
|
||||
)
|
||||
}
|
||||
|
||||
fn accounts_to_token_accounts(
|
||||
&self,
|
||||
commitment_config: CommitmentConfig,
|
||||
pubkey_accounts: Vec<(Pubkey, Account)>,
|
||||
) -> Vec<(Pubkey, UiTokenAccount)> {
|
||||
let mut mint_decimals: HashMap<Pubkey, u8> = HashMap::new();
|
||||
pubkey_accounts
|
||||
.into_iter()
|
||||
.filter_map(|(pubkey, account)| {
|
||||
let mint_pubkey = get_token_account_mint(&account.data)?;
|
||||
let decimals = mint_decimals.get(&mint_pubkey).cloned().or_else(|| {
|
||||
let mint = self
|
||||
.get_token_mint_with_commitment(&mint_pubkey, commitment_config)
|
||||
.ok()
|
||||
.map(|response| response.value)
|
||||
.flatten()?;
|
||||
mint_decimals.insert(mint_pubkey, mint.decimals);
|
||||
Some(mint.decimals)
|
||||
})?;
|
||||
match parse_token(&account.data, Some(decimals)) {
|
||||
Ok(TokenAccountType::Account(ui_token_account)) => {
|
||||
Some((pubkey, ui_token_account))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn poll_balance_with_timeout_and_commitment(
|
||||
&self,
|
||||
pubkey: &Pubkey,
|
||||
@@ -1009,6 +1301,31 @@ pub fn get_rpc_request_str(rpc_addr: SocketAddr, tls: bool) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_keyed_accounts(
|
||||
accounts: Vec<RpcKeyedAccount>,
|
||||
request: RpcRequest,
|
||||
) -> ClientResult<Vec<(Pubkey, Account)>> {
|
||||
let mut pubkey_accounts: Vec<(Pubkey, Account)> = Vec::new();
|
||||
for RpcKeyedAccount { pubkey, account } in accounts.into_iter() {
|
||||
let pubkey = pubkey.parse().map_err(|_| {
|
||||
ClientError::new_with_request(
|
||||
RpcError::ParseError("Pubkey".to_string()).into(),
|
||||
request,
|
||||
)
|
||||
})?;
|
||||
pubkey_accounts.push((
|
||||
pubkey,
|
||||
account.decode().ok_or_else(|| {
|
||||
ClientError::new_with_request(
|
||||
RpcError::ParseError("Account from rpc".to_string()).into(),
|
||||
request,
|
||||
)
|
||||
})?,
|
||||
));
|
||||
}
|
||||
Ok(pubkey_accounts)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@@ -58,3 +58,17 @@ pub struct RpcProgramAccountsConfig {
|
||||
#[serde(flatten)]
|
||||
pub account_config: RpcAccountInfoConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum RpcTokenAccountsFilter {
|
||||
Mint(String),
|
||||
ProgramId(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcGetConfirmedSignaturesForAddress2Config {
|
||||
pub before: Option<String>, // Signature as base-58 string
|
||||
pub limit: Option<usize>,
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
use serde_json::{json, Value};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use std::fmt;
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -13,6 +14,7 @@ pub enum RpcRequest {
|
||||
GetConfirmedBlock,
|
||||
GetConfirmedBlocks,
|
||||
GetConfirmedSignaturesForAddress,
|
||||
GetConfirmedSignaturesForAddress2,
|
||||
GetConfirmedTransaction,
|
||||
GetEpochInfo,
|
||||
GetEpochSchedule,
|
||||
@@ -36,6 +38,10 @@ pub enum RpcRequest {
|
||||
GetSlotsPerSegment,
|
||||
GetStoragePubkeysForSlot,
|
||||
GetSupply,
|
||||
GetTokenAccountBalance,
|
||||
GetTokenAccountsByDelegate,
|
||||
GetTokenAccountsByOwner,
|
||||
GetTokenSupply,
|
||||
GetTotalSupply,
|
||||
GetTransactionCount,
|
||||
GetVersion,
|
||||
@@ -60,6 +66,7 @@ impl fmt::Display for RpcRequest {
|
||||
RpcRequest::GetConfirmedBlock => "getConfirmedBlock",
|
||||
RpcRequest::GetConfirmedBlocks => "getConfirmedBlocks",
|
||||
RpcRequest::GetConfirmedSignaturesForAddress => "getConfirmedSignaturesForAddress",
|
||||
RpcRequest::GetConfirmedSignaturesForAddress2 => "getConfirmedSignaturesForAddress2",
|
||||
RpcRequest::GetConfirmedTransaction => "getConfirmedTransaction",
|
||||
RpcRequest::GetEpochInfo => "getEpochInfo",
|
||||
RpcRequest::GetEpochSchedule => "getEpochSchedule",
|
||||
@@ -83,6 +90,10 @@ impl fmt::Display for RpcRequest {
|
||||
RpcRequest::GetSlotsPerSegment => "getSlotsPerSegment",
|
||||
RpcRequest::GetStoragePubkeysForSlot => "getStoragePubkeysForSlot",
|
||||
RpcRequest::GetSupply => "getSupply",
|
||||
RpcRequest::GetTokenAccountBalance => "getTokenAccountBalance",
|
||||
RpcRequest::GetTokenAccountsByDelegate => "getTokenAccountsByDelegate",
|
||||
RpcRequest::GetTokenAccountsByOwner => "getTokenAccountsByOwner",
|
||||
RpcRequest::GetTokenSupply => "getTokenSupply",
|
||||
RpcRequest::GetTotalSupply => "getTotalSupply",
|
||||
RpcRequest::GetTransactionCount => "getTransactionCount",
|
||||
RpcRequest::GetVersion => "getVersion",
|
||||
@@ -103,6 +114,7 @@ pub const NUM_LARGEST_ACCOUNTS: usize = 20;
|
||||
pub const MAX_GET_SIGNATURE_STATUSES_QUERY_ITEMS: usize = 256;
|
||||
pub const MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS_SLOT_RANGE: u64 = 10_000;
|
||||
pub const MAX_GET_CONFIRMED_BLOCKS_RANGE: u64 = 500_000;
|
||||
pub const MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS2_LIMIT: usize = 1_000;
|
||||
|
||||
// Validators that are this number of slots behind are considered delinquent
|
||||
pub const DELINQUENT_VALIDATOR_SLOT_DISTANCE: u64 = 128;
|
||||
@@ -131,9 +143,16 @@ pub enum RpcError {
|
||||
ForUser(String), /* "direct-to-user message" */
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub enum TokenAccountsFilter {
|
||||
Mint(Pubkey),
|
||||
ProgramId(Pubkey),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::rpc_config::RpcTokenAccountsFilter;
|
||||
use solana_sdk::commitment_config::{CommitmentConfig, CommitmentLevel};
|
||||
|
||||
#[test]
|
||||
@@ -198,5 +217,16 @@ mod tests {
|
||||
let request =
|
||||
test_request.build_request_json(1, json!([addr.clone(), commitment_config.clone()]));
|
||||
assert_eq!(request["params"], json!([addr, commitment_config]));
|
||||
|
||||
// Test request with CommitmentConfig and params
|
||||
let test_request = RpcRequest::GetTokenAccountsByOwner;
|
||||
let mint = Pubkey::new_rand();
|
||||
let token_account_filter = RpcTokenAccountsFilter::Mint(mint.to_string());
|
||||
let request = test_request
|
||||
.build_request_json(1, json!([addr, token_account_filter, commitment_config]));
|
||||
assert_eq!(
|
||||
request["params"],
|
||||
json!([addr, token_account_filter, commitment_config])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -1,11 +1,12 @@
|
||||
use crate::client_error;
|
||||
use solana_account_decoder::UiAccount;
|
||||
use solana_account_decoder::{parse_token::UiTokenAmount, UiAccount};
|
||||
use solana_sdk::{
|
||||
clock::{Epoch, Slot},
|
||||
fee_calculator::{FeeCalculator, FeeRateGovernor},
|
||||
inflation::Inflation,
|
||||
transaction::{Result, TransactionError},
|
||||
};
|
||||
use solana_transaction_status::ConfirmedTransactionStatusWithSignature;
|
||||
use std::{collections::HashMap, net::SocketAddr};
|
||||
|
||||
pub type RpcResult<T> = client_error::Result<Response<T>>;
|
||||
@@ -219,3 +220,37 @@ pub struct RpcStakeActivation {
|
||||
pub active: u64,
|
||||
pub inactive: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcTokenAccountBalance {
|
||||
pub address: String,
|
||||
#[serde(flatten)]
|
||||
pub amount: UiTokenAmount,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcConfirmedTransactionStatusWithSignature {
|
||||
pub signature: String,
|
||||
pub slot: Slot,
|
||||
pub err: Option<TransactionError>,
|
||||
pub memo: Option<String>,
|
||||
}
|
||||
|
||||
impl From<ConfirmedTransactionStatusWithSignature> for RpcConfirmedTransactionStatusWithSignature {
|
||||
fn from(value: ConfirmedTransactionStatusWithSignature) -> Self {
|
||||
let ConfirmedTransactionStatusWithSignature {
|
||||
signature,
|
||||
slot,
|
||||
err,
|
||||
memo,
|
||||
} = value;
|
||||
Self {
|
||||
signature: signature.to_string(),
|
||||
slot,
|
||||
err,
|
||||
memo,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "solana-core"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.2.12"
|
||||
version = "1.2.21"
|
||||
documentation = "https://docs.rs/solana"
|
||||
homepage = "https://solana.com/"
|
||||
readme = "../README.md"
|
||||
@@ -21,7 +21,7 @@ byteorder = "1.3.4"
|
||||
chrono = { version = "0.4.11", features = ["serde"] }
|
||||
core_affinity = "0.5.10"
|
||||
crossbeam-channel = "0.4"
|
||||
ed25519-dalek = "=1.0.0-pre.3"
|
||||
ed25519-dalek = "=1.0.0-pre.4"
|
||||
fs_extra = "1.1.0"
|
||||
flate2 = "1.0"
|
||||
indexmap = "1.3"
|
||||
@@ -42,36 +42,38 @@ regex = "1.3.7"
|
||||
serde = "1.0.110"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.53"
|
||||
solana-account-decoder = { path = "../account-decoder", version = "1.2.12" }
|
||||
solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "1.2.12" }
|
||||
solana-budget-program = { path = "../programs/budget", version = "1.2.12" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.12" }
|
||||
solana-client = { path = "../client", version = "1.2.12" }
|
||||
solana-faucet = { path = "../faucet", version = "1.2.12" }
|
||||
solana-genesis-programs = { path = "../genesis-programs", version = "1.2.12" }
|
||||
solana-ledger = { path = "../ledger", version = "1.2.12" }
|
||||
solana-logger = { path = "../logger", version = "1.2.12" }
|
||||
solana-merkle-tree = { path = "../merkle-tree", version = "1.2.12" }
|
||||
solana-metrics = { path = "../metrics", version = "1.2.12" }
|
||||
solana-measure = { path = "../measure", version = "1.2.12" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.2.12" }
|
||||
solana-perf = { path = "../perf", version = "1.2.12" }
|
||||
solana-runtime = { path = "../runtime", version = "1.2.12" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.12" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.2.12" }
|
||||
solana-streamer = { path = "../streamer", version = "1.2.12" }
|
||||
solana-sys-tuner = { path = "../sys-tuner", version = "1.2.12" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "1.2.12" }
|
||||
solana-version = { path = "../version", version = "1.2.12" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.2.12" }
|
||||
solana-vote-signer = { path = "../vote-signer", version = "1.2.12" }
|
||||
solana-account-decoder = { path = "../account-decoder", version = "1.2.21" }
|
||||
solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "1.2.21" }
|
||||
solana-budget-program = { path = "../programs/budget", version = "1.2.21" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.21" }
|
||||
solana-client = { path = "../client", version = "1.2.21" }
|
||||
solana-faucet = { path = "../faucet", version = "1.2.21" }
|
||||
solana-genesis-programs = { path = "../genesis-programs", version = "1.2.21" }
|
||||
solana-ledger = { path = "../ledger", version = "1.2.21" }
|
||||
solana-logger = { path = "../logger", version = "1.2.21" }
|
||||
solana-merkle-tree = { path = "../merkle-tree", version = "1.2.21" }
|
||||
solana-metrics = { path = "../metrics", version = "1.2.21" }
|
||||
solana-measure = { path = "../measure", version = "1.2.21" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.2.21" }
|
||||
solana-perf = { path = "../perf", version = "1.2.21" }
|
||||
solana-runtime = { path = "../runtime", version = "1.2.21" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.21" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.2.21" }
|
||||
solana-storage-bigtable = { path = "../storage-bigtable", version = "1.2.21" }
|
||||
solana-streamer = { path = "../streamer", version = "1.2.21" }
|
||||
solana-sys-tuner = { path = "../sys-tuner", version = "1.2.21" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "1.2.21" }
|
||||
solana-version = { path = "../version", version = "1.2.21" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.2.21" }
|
||||
solana-vote-signer = { path = "../vote-signer", version = "1.2.21" }
|
||||
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "1.2.21" }
|
||||
spl-token-v1-0 = { package = "spl-token", version = "1.0.6", features = ["skip-no-mangle"] }
|
||||
tempfile = "3.1.0"
|
||||
thiserror = "1.0"
|
||||
tokio = "0.1"
|
||||
tokio-codec = "0.1"
|
||||
tokio-fs = "0.1"
|
||||
tokio-io = "0.1"
|
||||
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "1.2.12" }
|
||||
tokio = { version = "0.2.22", features = ["full"] }
|
||||
tokio_01 = { version = "0.1", package = "tokio" }
|
||||
tokio_fs_01 = { version = "0.1", package = "tokio-fs" }
|
||||
tokio_io_01 = { version = "0.1", package = "tokio-io" }
|
||||
trees = "0.2.1"
|
||||
|
||||
[dev-dependencies]
|
||||
|
@@ -509,7 +509,7 @@ impl BankingStage {
|
||||
// expires.
|
||||
let txs = batch.transactions();
|
||||
let pre_balances = if transaction_status_sender.is_some() {
|
||||
bank.collect_balances(txs)
|
||||
bank.collect_balances(batch)
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
@@ -545,7 +545,7 @@ impl BankingStage {
|
||||
.processing_results;
|
||||
|
||||
if let Some(sender) = transaction_status_sender {
|
||||
let post_balances = bank.collect_balances(txs);
|
||||
let post_balances = bank.collect_balances(batch);
|
||||
send_transaction_status_batch(
|
||||
bank.clone(),
|
||||
batch.transactions(),
|
||||
|
@@ -23,14 +23,14 @@ pub fn calculate_non_circulating_supply(bank: &Arc<Bank>) -> NonCirculatingSuppl
|
||||
let stake_account = StakeState::from(&account).unwrap_or_default();
|
||||
match stake_account {
|
||||
StakeState::Initialized(meta) => {
|
||||
if meta.lockup.is_in_force(&clock, &HashSet::default())
|
||||
if meta.lockup.is_in_force(&clock, None)
|
||||
|| withdraw_authority_list.contains(&meta.authorized.withdrawer)
|
||||
{
|
||||
non_circulating_accounts_set.insert(*pubkey);
|
||||
}
|
||||
}
|
||||
StakeState::Stake(meta, _stake) => {
|
||||
if meta.lockup.is_in_force(&clock, &HashSet::default())
|
||||
if meta.lockup.is_in_force(&clock, None)
|
||||
|| withdraw_authority_list.contains(&meta.authorized.withdrawer)
|
||||
{
|
||||
non_circulating_accounts_set.insert(*pubkey);
|
||||
@@ -77,6 +77,7 @@ solana_sdk::pubkeys!(
|
||||
"5q54XjQ7vDx4y6KphPeE97LUNiYGtP55spjvXAWPGBuf",
|
||||
"3o6xgkJ9sTmDeQWyfj3sxwon18fXJB9PV5LDc8sfgR4a",
|
||||
"GumSE5HsMV5HCwBTv2D2D81yy9x17aDkvobkqAfTRgmo",
|
||||
"AzVV9ZZDxTgW4wWfJmsG6ytaHpQGSe1yz76Nyy84VbQF",
|
||||
]
|
||||
);
|
||||
|
||||
|
@@ -1863,7 +1863,8 @@ impl ReplayStage {
|
||||
pub fn get_unlock_switch_vote_slot(operating_mode: OperatingMode) -> Slot {
|
||||
match operating_mode {
|
||||
OperatingMode::Development => 0,
|
||||
OperatingMode::Stable => std::u64::MAX / 2,
|
||||
// 400_000 slots into epoch 61
|
||||
OperatingMode::Stable => 26_752_000,
|
||||
// Epoch 63
|
||||
OperatingMode::Preview => 21_692_256,
|
||||
}
|
||||
@@ -1872,7 +1873,8 @@ impl ReplayStage {
|
||||
pub fn get_unlock_heaviest_subtree_fork_choice(operating_mode: OperatingMode) -> Slot {
|
||||
match operating_mode {
|
||||
OperatingMode::Development => 0,
|
||||
OperatingMode::Stable => std::u64::MAX / 2,
|
||||
// 400_000 slots into epoch 61
|
||||
OperatingMode::Stable => 26_752_000,
|
||||
// Epoch 63
|
||||
OperatingMode::Preview => 21_692_256,
|
||||
}
|
||||
@@ -1948,146 +1950,179 @@ pub(crate) mod tests {
|
||||
assert!(ReplayStage::is_partition_detected(&ancestors, 4, 3));
|
||||
}
|
||||
|
||||
struct ReplayBlockstoreComponents {
|
||||
blockstore: Arc<Blockstore>,
|
||||
validator_voting_keys: HashMap<Pubkey, Pubkey>,
|
||||
progress: ProgressMap,
|
||||
bank_forks: Arc<RwLock<BankForks>>,
|
||||
leader_schedule_cache: Arc<LeaderScheduleCache>,
|
||||
rpc_subscriptions: Arc<RpcSubscriptions>,
|
||||
}
|
||||
|
||||
fn replay_blockstore_components() -> ReplayBlockstoreComponents {
|
||||
// Setup blockstore
|
||||
let ledger_path = get_tmp_ledger_path!();
|
||||
let blockstore = Arc::new(
|
||||
Blockstore::open(&ledger_path).expect("Expected to be able to open database ledger"),
|
||||
);
|
||||
let validator_authorized_voter_keypairs: Vec<_> =
|
||||
(0..20).map(|_| ValidatorVoteKeypairs::new_rand()).collect();
|
||||
|
||||
let validator_voting_keys: HashMap<_, _> = validator_authorized_voter_keypairs
|
||||
.iter()
|
||||
.map(|v| (v.node_keypair.pubkey(), v.vote_keypair.pubkey()))
|
||||
.collect();
|
||||
let GenesisConfigInfo { genesis_config, .. } =
|
||||
genesis_utils::create_genesis_config_with_vote_accounts(
|
||||
10_000,
|
||||
&validator_authorized_voter_keypairs,
|
||||
100,
|
||||
);
|
||||
|
||||
let bank0 = Bank::new(&genesis_config);
|
||||
|
||||
// ProgressMap
|
||||
let mut progress = ProgressMap::default();
|
||||
progress.insert(
|
||||
0,
|
||||
ForkProgress::new_from_bank(
|
||||
&bank0,
|
||||
bank0.collector_id(),
|
||||
&Pubkey::default(),
|
||||
None,
|
||||
0,
|
||||
0,
|
||||
),
|
||||
);
|
||||
|
||||
// Leader schedule cache
|
||||
let leader_schedule_cache = Arc::new(LeaderScheduleCache::new_from_bank(&bank0));
|
||||
|
||||
// BankForks
|
||||
let bank_forks = Arc::new(RwLock::new(BankForks::new(bank0)));
|
||||
|
||||
// RpcSubscriptions
|
||||
let exit = Arc::new(AtomicBool::new(false));
|
||||
let rpc_subscriptions = Arc::new(RpcSubscriptions::new(
|
||||
&exit,
|
||||
bank_forks.clone(),
|
||||
Arc::new(RwLock::new(BlockCommitmentCache::default_with_blockstore(
|
||||
blockstore.clone(),
|
||||
))),
|
||||
));
|
||||
|
||||
ReplayBlockstoreComponents {
|
||||
blockstore,
|
||||
validator_voting_keys,
|
||||
progress,
|
||||
bank_forks,
|
||||
leader_schedule_cache,
|
||||
rpc_subscriptions,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_child_slots_of_same_parent() {
|
||||
let ledger_path = get_tmp_ledger_path!();
|
||||
{
|
||||
// Setup
|
||||
let blockstore = Arc::new(
|
||||
Blockstore::open(&ledger_path)
|
||||
.expect("Expected to be able to open database ledger"),
|
||||
);
|
||||
let validator_authorized_voter_keypairs: Vec<_> = (0..20)
|
||||
.map(|_| ValidatorVoteKeypairs::new(Keypair::new(), Keypair::new(), Keypair::new()))
|
||||
.collect();
|
||||
let ReplayBlockstoreComponents {
|
||||
blockstore,
|
||||
validator_voting_keys,
|
||||
mut progress,
|
||||
bank_forks,
|
||||
leader_schedule_cache,
|
||||
rpc_subscriptions,
|
||||
} = replay_blockstore_components();
|
||||
|
||||
let validator_voting_keys: HashMap<_, _> = validator_authorized_voter_keypairs
|
||||
.iter()
|
||||
.map(|v| (v.node_keypair.pubkey(), v.vote_keypair.pubkey()))
|
||||
.collect();
|
||||
let GenesisConfigInfo { genesis_config, .. } =
|
||||
genesis_utils::create_genesis_config_with_vote_accounts(
|
||||
10_000,
|
||||
&validator_authorized_voter_keypairs,
|
||||
100,
|
||||
);
|
||||
let bank0 = Bank::new(&genesis_config);
|
||||
let mut progress = ProgressMap::default();
|
||||
progress.insert(
|
||||
// Insert a non-root bank so that the propagation logic will update this
|
||||
// bank
|
||||
let bank1 = Bank::new_from_parent(
|
||||
bank_forks.read().unwrap().get(0).unwrap(),
|
||||
&leader_schedule_cache.slot_leader_at(1, None).unwrap(),
|
||||
1,
|
||||
);
|
||||
progress.insert(
|
||||
1,
|
||||
ForkProgress::new_from_bank(
|
||||
&bank1,
|
||||
bank1.collector_id(),
|
||||
validator_voting_keys.get(&bank1.collector_id()).unwrap(),
|
||||
Some(0),
|
||||
0,
|
||||
ForkProgress::new_from_bank(
|
||||
&bank0,
|
||||
bank0.collector_id(),
|
||||
&Pubkey::default(),
|
||||
None,
|
||||
0,
|
||||
0,
|
||||
),
|
||||
);
|
||||
let leader_schedule_cache = Arc::new(LeaderScheduleCache::new_from_bank(&bank0));
|
||||
let exit = Arc::new(AtomicBool::new(false));
|
||||
let mut bank_forks = BankForks::new(bank0);
|
||||
0,
|
||||
),
|
||||
);
|
||||
assert!(progress.get_propagated_stats(1).unwrap().is_leader_slot);
|
||||
bank1.freeze();
|
||||
bank_forks.write().unwrap().insert(bank1);
|
||||
|
||||
// Insert a non-root bank so that the propagation logic will update this
|
||||
// bank
|
||||
let bank1 = Bank::new_from_parent(
|
||||
bank_forks.get(0).unwrap(),
|
||||
&leader_schedule_cache.slot_leader_at(1, None).unwrap(),
|
||||
1,
|
||||
);
|
||||
progress.insert(
|
||||
1,
|
||||
ForkProgress::new_from_bank(
|
||||
&bank1,
|
||||
bank1.collector_id(),
|
||||
&validator_voting_keys.get(&bank1.collector_id()).unwrap(),
|
||||
Some(0),
|
||||
0,
|
||||
0,
|
||||
),
|
||||
);
|
||||
assert!(progress.get_propagated_stats(1).unwrap().is_leader_slot);
|
||||
bank1.freeze();
|
||||
bank_forks.insert(bank1);
|
||||
let bank_forks = Arc::new(RwLock::new(bank_forks));
|
||||
let subscriptions = Arc::new(RpcSubscriptions::new(
|
||||
&exit,
|
||||
bank_forks.clone(),
|
||||
Arc::new(RwLock::new(BlockCommitmentCache::default_with_blockstore(
|
||||
blockstore.clone(),
|
||||
))),
|
||||
));
|
||||
// Insert shreds for slot NUM_CONSECUTIVE_LEADER_SLOTS,
|
||||
// chaining to slot 1
|
||||
let (shreds, _) = make_slot_entries(NUM_CONSECUTIVE_LEADER_SLOTS, 1, 8);
|
||||
blockstore.insert_shreds(shreds, None, false).unwrap();
|
||||
assert!(bank_forks
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(NUM_CONSECUTIVE_LEADER_SLOTS)
|
||||
.is_none());
|
||||
ReplayStage::generate_new_bank_forks(
|
||||
&blockstore,
|
||||
&bank_forks,
|
||||
&leader_schedule_cache,
|
||||
&rpc_subscriptions,
|
||||
None,
|
||||
&mut progress,
|
||||
&mut PubkeyReferences::default(),
|
||||
);
|
||||
assert!(bank_forks
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(NUM_CONSECUTIVE_LEADER_SLOTS)
|
||||
.is_some());
|
||||
|
||||
// Insert shreds for slot NUM_CONSECUTIVE_LEADER_SLOTS,
|
||||
// chaining to slot 1
|
||||
let (shreds, _) = make_slot_entries(NUM_CONSECUTIVE_LEADER_SLOTS, 1, 8);
|
||||
blockstore.insert_shreds(shreds, None, false).unwrap();
|
||||
assert!(bank_forks
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(NUM_CONSECUTIVE_LEADER_SLOTS)
|
||||
.is_none());
|
||||
ReplayStage::generate_new_bank_forks(
|
||||
&blockstore,
|
||||
&bank_forks,
|
||||
&leader_schedule_cache,
|
||||
&subscriptions,
|
||||
None,
|
||||
&mut progress,
|
||||
&mut PubkeyReferences::default(),
|
||||
);
|
||||
assert!(bank_forks
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(NUM_CONSECUTIVE_LEADER_SLOTS)
|
||||
.is_some());
|
||||
// Insert shreds for slot 2 * NUM_CONSECUTIVE_LEADER_SLOTS,
|
||||
// chaining to slot 1
|
||||
let (shreds, _) = make_slot_entries(2 * NUM_CONSECUTIVE_LEADER_SLOTS, 1, 8);
|
||||
blockstore.insert_shreds(shreds, None, false).unwrap();
|
||||
assert!(bank_forks
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(2 * NUM_CONSECUTIVE_LEADER_SLOTS)
|
||||
.is_none());
|
||||
ReplayStage::generate_new_bank_forks(
|
||||
&blockstore,
|
||||
&bank_forks,
|
||||
&leader_schedule_cache,
|
||||
&rpc_subscriptions,
|
||||
None,
|
||||
&mut progress,
|
||||
&mut PubkeyReferences::default(),
|
||||
);
|
||||
assert!(bank_forks
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(NUM_CONSECUTIVE_LEADER_SLOTS)
|
||||
.is_some());
|
||||
assert!(bank_forks
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(2 * NUM_CONSECUTIVE_LEADER_SLOTS)
|
||||
.is_some());
|
||||
|
||||
// Insert shreds for slot 2 * NUM_CONSECUTIVE_LEADER_SLOTS,
|
||||
// chaining to slot 1
|
||||
let (shreds, _) = make_slot_entries(2 * NUM_CONSECUTIVE_LEADER_SLOTS, 1, 8);
|
||||
blockstore.insert_shreds(shreds, None, false).unwrap();
|
||||
assert!(bank_forks
|
||||
.read()
|
||||
// // There are 20 equally staked accounts, of which 3 have built
|
||||
// banks above or at bank 1. Because 3/20 < SUPERMINORITY_THRESHOLD,
|
||||
// we should see 3 validators in bank 1's propagated_validator set.
|
||||
let expected_leader_slots = vec![
|
||||
1,
|
||||
NUM_CONSECUTIVE_LEADER_SLOTS,
|
||||
2 * NUM_CONSECUTIVE_LEADER_SLOTS,
|
||||
];
|
||||
for slot in expected_leader_slots {
|
||||
let leader = leader_schedule_cache.slot_leader_at(slot, None).unwrap();
|
||||
let vote_key = validator_voting_keys.get(&leader).unwrap();
|
||||
assert!(progress
|
||||
.get_propagated_stats(1)
|
||||
.unwrap()
|
||||
.get(2 * NUM_CONSECUTIVE_LEADER_SLOTS)
|
||||
.is_none());
|
||||
ReplayStage::generate_new_bank_forks(
|
||||
&blockstore,
|
||||
&bank_forks,
|
||||
&leader_schedule_cache,
|
||||
&subscriptions,
|
||||
None,
|
||||
&mut progress,
|
||||
&mut PubkeyReferences::default(),
|
||||
);
|
||||
assert!(bank_forks
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(NUM_CONSECUTIVE_LEADER_SLOTS)
|
||||
.is_some());
|
||||
assert!(bank_forks
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(2 * NUM_CONSECUTIVE_LEADER_SLOTS)
|
||||
.is_some());
|
||||
|
||||
// // There are 20 equally staked acccounts, of which 3 have built
|
||||
// banks above or at bank 1. Because 3/20 < SUPERMINORITY_THRESHOLD,
|
||||
// we should see 3 validators in bank 1's propagated_validator set.
|
||||
let expected_leader_slots = vec![
|
||||
1,
|
||||
NUM_CONSECUTIVE_LEADER_SLOTS,
|
||||
2 * NUM_CONSECUTIVE_LEADER_SLOTS,
|
||||
];
|
||||
for slot in expected_leader_slots {
|
||||
let leader = leader_schedule_cache.slot_leader_at(slot, None).unwrap();
|
||||
let vote_key = validator_voting_keys.get(&leader).unwrap();
|
||||
assert!(progress
|
||||
.get_propagated_stats(1)
|
||||
.unwrap()
|
||||
.propagated_validators
|
||||
.contains(vote_key));
|
||||
}
|
||||
.propagated_validators
|
||||
.contains(vote_key));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2811,10 +2846,7 @@ pub(crate) mod tests {
|
||||
&replay_votes_sender,
|
||||
);
|
||||
// No new stats should have been computed
|
||||
assert!(replay_votes_receiver
|
||||
.try_iter()
|
||||
.collect::<Vec<_>>()
|
||||
.is_empty());
|
||||
assert!(replay_votes_receiver.try_iter().next().is_none());
|
||||
assert!(newly_computed.is_empty());
|
||||
}
|
||||
|
||||
|
1196
core/src/rpc.rs
1196
core/src/rpc.rs
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,7 @@ use solana_sdk::clock::Slot;
|
||||
const JSON_RPC_SERVER_ERROR_0: i64 = -32000;
|
||||
const JSON_RPC_SERVER_ERROR_1: i64 = -32001;
|
||||
const JSON_RPC_SERVER_ERROR_2: i64 = -32002;
|
||||
const JSON_RPC_SERVER_ERROR_3: i64 = -32003;
|
||||
|
||||
pub enum RpcCustomError {
|
||||
NonexistentClusterRoot {
|
||||
@@ -17,6 +18,7 @@ pub enum RpcCustomError {
|
||||
SendTransactionPreflightFailure {
|
||||
message: String,
|
||||
},
|
||||
SendTransactionIsNotSigned,
|
||||
}
|
||||
|
||||
impl From<RpcCustomError> for Error {
|
||||
@@ -49,6 +51,11 @@ impl From<RpcCustomError> for Error {
|
||||
message,
|
||||
data: None,
|
||||
},
|
||||
RpcCustomError::SendTransactionIsNotSigned => Self {
|
||||
code: ErrorCode::ServerError(JSON_RPC_SERVER_ERROR_3),
|
||||
message: "Transaction is not signed".to_string(),
|
||||
data: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -5,7 +5,10 @@ use jsonrpc_core::{Error, ErrorCode, Result};
|
||||
use jsonrpc_derive::rpc;
|
||||
use jsonrpc_pubsub::{typed::Subscriber, Session, SubscriptionId};
|
||||
use solana_account_decoder::UiAccount;
|
||||
use solana_client::rpc_response::{Response as RpcResponse, RpcKeyedAccount, RpcSignatureResult};
|
||||
use solana_client::{
|
||||
rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig},
|
||||
rpc_response::{Response as RpcResponse, RpcKeyedAccount, RpcSignatureResult},
|
||||
};
|
||||
#[cfg(test)]
|
||||
use solana_ledger::{bank_forks::BankForks, blockstore::Blockstore};
|
||||
use solana_sdk::{
|
||||
@@ -38,7 +41,7 @@ pub trait RpcSolPubSub {
|
||||
meta: Self::Metadata,
|
||||
subscriber: Subscriber<RpcResponse<UiAccount>>,
|
||||
pubkey_str: String,
|
||||
commitment: Option<CommitmentConfig>,
|
||||
config: Option<RpcAccountInfoConfig>,
|
||||
);
|
||||
|
||||
// Unsubscribe from account notification subscription.
|
||||
@@ -62,7 +65,7 @@ pub trait RpcSolPubSub {
|
||||
meta: Self::Metadata,
|
||||
subscriber: Subscriber<RpcResponse<RpcKeyedAccount>>,
|
||||
pubkey_str: String,
|
||||
commitment: Option<CommitmentConfig>,
|
||||
config: Option<RpcProgramAccountsConfig>,
|
||||
);
|
||||
|
||||
// Unsubscribe from account notification subscription.
|
||||
@@ -178,7 +181,7 @@ impl RpcSolPubSub for RpcSolPubSubImpl {
|
||||
_meta: Self::Metadata,
|
||||
subscriber: Subscriber<RpcResponse<UiAccount>>,
|
||||
pubkey_str: String,
|
||||
commitment: Option<CommitmentConfig>,
|
||||
config: Option<RpcAccountInfoConfig>,
|
||||
) {
|
||||
match param::<Pubkey>(&pubkey_str, "pubkey") {
|
||||
Ok(pubkey) => {
|
||||
@@ -186,7 +189,7 @@ impl RpcSolPubSub for RpcSolPubSubImpl {
|
||||
let sub_id = SubscriptionId::Number(id as u64);
|
||||
info!("account_subscribe: account={:?} id={:?}", pubkey, sub_id);
|
||||
self.subscriptions
|
||||
.add_account_subscription(pubkey, commitment, sub_id, subscriber)
|
||||
.add_account_subscription(pubkey, config, sub_id, subscriber)
|
||||
}
|
||||
Err(e) => subscriber.reject(e).unwrap(),
|
||||
}
|
||||
@@ -214,7 +217,7 @@ impl RpcSolPubSub for RpcSolPubSubImpl {
|
||||
_meta: Self::Metadata,
|
||||
subscriber: Subscriber<RpcResponse<RpcKeyedAccount>>,
|
||||
pubkey_str: String,
|
||||
commitment: Option<CommitmentConfig>,
|
||||
config: Option<RpcProgramAccountsConfig>,
|
||||
) {
|
||||
match param::<Pubkey>(&pubkey_str, "pubkey") {
|
||||
Ok(pubkey) => {
|
||||
@@ -222,7 +225,7 @@ impl RpcSolPubSub for RpcSolPubSubImpl {
|
||||
let sub_id = SubscriptionId::Number(id as u64);
|
||||
info!("program_subscribe: account={:?} id={:?}", pubkey, sub_id);
|
||||
self.subscriptions
|
||||
.add_program_subscription(pubkey, commitment, sub_id, subscriber)
|
||||
.add_program_subscription(pubkey, config, sub_id, subscriber)
|
||||
}
|
||||
Err(e) => subscriber.reject(e).unwrap(),
|
||||
}
|
||||
@@ -361,6 +364,7 @@ mod tests {
|
||||
use jsonrpc_core::{futures::sync::mpsc, Response};
|
||||
use jsonrpc_pubsub::{PubSubHandler, Session};
|
||||
use serial_test_derive::serial;
|
||||
use solana_account_decoder::{parse_account_data::parse_account_data, UiAccountEncoding};
|
||||
use solana_budget_program::{self, budget_instruction};
|
||||
use solana_ledger::{
|
||||
bank_forks::BankForks,
|
||||
@@ -376,7 +380,7 @@ mod tests {
|
||||
message::Message,
|
||||
pubkey::Pubkey,
|
||||
signature::{Keypair, Signer},
|
||||
system_program, system_transaction,
|
||||
system_instruction, system_program, system_transaction,
|
||||
transaction::{self, Transaction},
|
||||
};
|
||||
use solana_vote_program::vote_transaction;
|
||||
@@ -555,7 +559,10 @@ mod tests {
|
||||
session,
|
||||
subscriber,
|
||||
contract_state.pubkey().to_string(),
|
||||
Some(CommitmentConfig::recent()),
|
||||
Some(RpcAccountInfoConfig {
|
||||
commitment: Some(CommitmentConfig::recent()),
|
||||
encoding: None,
|
||||
}),
|
||||
);
|
||||
|
||||
let tx = system_transaction::transfer(&alice, &contract_funds.pubkey(), 51, blockhash);
|
||||
@@ -628,6 +635,100 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_account_subscribe_with_encoding() {
|
||||
let GenesisConfigInfo {
|
||||
genesis_config,
|
||||
mint_keypair: alice,
|
||||
..
|
||||
} = create_genesis_config(10_000);
|
||||
|
||||
let nonce_account = Keypair::new();
|
||||
let bank = Bank::new(&genesis_config);
|
||||
let blockhash = bank.last_blockhash();
|
||||
let bank_forks = Arc::new(RwLock::new(BankForks::new(bank)));
|
||||
let bank0 = bank_forks.read().unwrap().get(0).unwrap().clone();
|
||||
let bank1 = Bank::new_from_parent(&bank0, &Pubkey::default(), 1);
|
||||
bank_forks.write().unwrap().insert(bank1);
|
||||
let ledger_path = get_tmp_ledger_path!();
|
||||
let blockstore = Arc::new(Blockstore::open(&ledger_path).unwrap());
|
||||
|
||||
let rpc = RpcSolPubSubImpl {
|
||||
subscriptions: Arc::new(RpcSubscriptions::new(
|
||||
&Arc::new(AtomicBool::new(false)),
|
||||
bank_forks.clone(),
|
||||
Arc::new(RwLock::new(
|
||||
BlockCommitmentCache::new_for_tests_with_blockstore_bank(
|
||||
blockstore,
|
||||
bank_forks.read().unwrap().get(1).unwrap().clone(),
|
||||
1,
|
||||
),
|
||||
)),
|
||||
)),
|
||||
uid: Arc::new(atomic::AtomicUsize::default()),
|
||||
};
|
||||
let session = create_session();
|
||||
let (subscriber, _id_receiver, receiver) = Subscriber::new_test("accountNotification");
|
||||
rpc.account_subscribe(
|
||||
session,
|
||||
subscriber,
|
||||
nonce_account.pubkey().to_string(),
|
||||
Some(RpcAccountInfoConfig {
|
||||
commitment: Some(CommitmentConfig::recent()),
|
||||
encoding: Some(UiAccountEncoding::JsonParsed),
|
||||
}),
|
||||
);
|
||||
|
||||
let ixs = system_instruction::create_nonce_account(
|
||||
&alice.pubkey(),
|
||||
&nonce_account.pubkey(),
|
||||
&alice.pubkey(),
|
||||
100,
|
||||
);
|
||||
let message = Message::new(&ixs, Some(&alice.pubkey()));
|
||||
let tx = Transaction::new(&[&alice, &nonce_account], message, blockhash);
|
||||
process_transaction_and_notify(&bank_forks, &tx, &rpc.subscriptions, 1).unwrap();
|
||||
sleep(Duration::from_millis(200));
|
||||
|
||||
// Test signature confirmation notification #1
|
||||
let expected_data = bank_forks
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(1)
|
||||
.unwrap()
|
||||
.get_account(&nonce_account.pubkey())
|
||||
.unwrap()
|
||||
.data;
|
||||
let expected_data = parse_account_data(
|
||||
&nonce_account.pubkey(),
|
||||
&system_program::id(),
|
||||
&expected_data,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let expected = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"method": "accountNotification",
|
||||
"params": {
|
||||
"result": {
|
||||
"context": { "slot": 1 },
|
||||
"value": {
|
||||
"owner": system_program::id().to_string(),
|
||||
"lamports": 100,
|
||||
"data": expected_data,
|
||||
"executable": false,
|
||||
"rentEpoch": 1,
|
||||
},
|
||||
},
|
||||
"subscription": 0,
|
||||
}
|
||||
});
|
||||
|
||||
let (response, _) = robust_poll_or_panic(receiver);
|
||||
assert_eq!(serde_json::to_string(&expected).unwrap(), response);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_account_unsubscribe() {
|
||||
@@ -702,7 +803,10 @@ mod tests {
|
||||
session,
|
||||
subscriber,
|
||||
bob.pubkey().to_string(),
|
||||
Some(CommitmentConfig::root()),
|
||||
Some(RpcAccountInfoConfig {
|
||||
commitment: Some(CommitmentConfig::root()),
|
||||
encoding: None,
|
||||
}),
|
||||
);
|
||||
|
||||
let tx = system_transaction::transfer(&alice, &bob.pubkey(), 100, blockhash);
|
||||
@@ -755,7 +859,10 @@ mod tests {
|
||||
session,
|
||||
subscriber,
|
||||
bob.pubkey().to_string(),
|
||||
Some(CommitmentConfig::root()),
|
||||
Some(RpcAccountInfoConfig {
|
||||
commitment: Some(CommitmentConfig::root()),
|
||||
encoding: None,
|
||||
}),
|
||||
);
|
||||
|
||||
let tx = system_transaction::transfer(&alice, &bob.pubkey(), 100, blockhash);
|
||||
|
@@ -24,7 +24,7 @@ use std::{
|
||||
sync::{mpsc::channel, Arc, RwLock},
|
||||
thread::{self, Builder, JoinHandle},
|
||||
};
|
||||
use tokio::prelude::Future;
|
||||
use tokio::runtime;
|
||||
|
||||
pub struct JsonRpcService {
|
||||
thread_hdl: JoinHandle<()>,
|
||||
@@ -33,6 +33,7 @@ pub struct JsonRpcService {
|
||||
pub request_processor: JsonRpcRequestProcessor, // Used only by test_rpc_new()...
|
||||
|
||||
close_handle: Option<CloseHandle>,
|
||||
runtime: runtime::Runtime,
|
||||
}
|
||||
|
||||
struct RpcRequestMiddleware {
|
||||
@@ -98,6 +99,9 @@ impl RpcRequestMiddleware {
|
||||
}
|
||||
|
||||
fn process_file_get(&self, path: &str) -> RequestMiddlewareAction {
|
||||
// Stuck on tokio 0.1 until the jsonrpc-http-server crate upgrades to tokio 0.2
|
||||
use tokio_01::prelude::*;
|
||||
|
||||
let stem = path.split_at(1).1; // Drop leading '/' from path
|
||||
let filename = {
|
||||
match path {
|
||||
@@ -116,10 +120,10 @@ impl RpcRequestMiddleware {
|
||||
RequestMiddlewareAction::Respond {
|
||||
should_validate_hosts: true,
|
||||
response: Box::new(
|
||||
tokio_fs::file::File::open(filename)
|
||||
tokio_fs_01::file::File::open(filename)
|
||||
.and_then(|file| {
|
||||
let buf: Vec<u8> = Vec::new();
|
||||
tokio_io::io::read_to_end(file, buf)
|
||||
tokio_io_01::io::read_to_end(file, buf)
|
||||
.and_then(|item| Ok(hyper::Response::new(item.1.into())))
|
||||
.or_else(|_| Ok(RpcRequestMiddleware::internal_server_error()))
|
||||
})
|
||||
@@ -256,6 +260,27 @@ impl JsonRpcService {
|
||||
&exit_send_transaction_service,
|
||||
));
|
||||
|
||||
let mut runtime = runtime::Builder::new()
|
||||
.threaded_scheduler()
|
||||
.thread_name("rpc-runtime")
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("Runtime");
|
||||
|
||||
let bigtable_ledger_storage = if config.enable_bigtable_ledger_storage {
|
||||
runtime
|
||||
.block_on(solana_storage_bigtable::LedgerStorage::new(false))
|
||||
.map(|x| {
|
||||
info!("BigTable ledger storage initialized");
|
||||
Some(x)
|
||||
})
|
||||
.unwrap_or_else(|err| {
|
||||
error!("Failed to initialize BigTable ledger storage: {:?}", err);
|
||||
None
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let request_processor = JsonRpcRequestProcessor::new(
|
||||
config,
|
||||
bank_forks.clone(),
|
||||
@@ -266,6 +291,8 @@ impl JsonRpcService {
|
||||
cluster_info,
|
||||
genesis_hash,
|
||||
send_transaction_service,
|
||||
&runtime,
|
||||
bigtable_ledger_storage,
|
||||
);
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -325,6 +352,7 @@ impl JsonRpcService {
|
||||
.register_exit(Box::new(move || close_handle_.close()));
|
||||
Self {
|
||||
thread_hdl,
|
||||
runtime,
|
||||
#[cfg(test)]
|
||||
request_processor: test_request_processor,
|
||||
close_handle: Some(close_handle),
|
||||
@@ -338,6 +366,7 @@ impl JsonRpcService {
|
||||
}
|
||||
|
||||
pub fn join(self) -> thread::Result<()> {
|
||||
self.runtime.shutdown_background();
|
||||
self.thread_hdl.join()
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,9 @@
|
||||
//! The `pubsub` module implements a threaded subscription service on client RPC request
|
||||
|
||||
use crate::commitment::BlockCommitmentCache;
|
||||
use crate::{
|
||||
commitment::BlockCommitmentCache,
|
||||
rpc::{get_parsed_token_account, get_parsed_token_accounts},
|
||||
};
|
||||
use core::hash::Hash;
|
||||
use jsonrpc_core::futures::Future;
|
||||
use jsonrpc_pubsub::{
|
||||
@@ -8,9 +11,11 @@ use jsonrpc_pubsub::{
|
||||
SubscriptionId,
|
||||
};
|
||||
use serde::Serialize;
|
||||
use solana_account_decoder::{UiAccount, UiAccountEncoding};
|
||||
use solana_client::rpc_response::{
|
||||
Response, RpcKeyedAccount, RpcResponseContext, RpcSignatureResult,
|
||||
use solana_account_decoder::{parse_token::spl_token_id_v1_0, UiAccount, UiAccountEncoding};
|
||||
use solana_client::{
|
||||
rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig},
|
||||
rpc_filter::RpcFilterType,
|
||||
rpc_response::{Response, RpcKeyedAccount, RpcResponseContext, RpcSignatureResult},
|
||||
};
|
||||
use solana_ledger::{bank_forks::BankForks, blockstore::Blockstore};
|
||||
use solana_runtime::bank::Bank;
|
||||
@@ -34,7 +39,9 @@ use std::{
|
||||
iter,
|
||||
sync::{Arc, Mutex, RwLock},
|
||||
};
|
||||
use tokio::runtime::{Builder as RuntimeBuilder, Runtime, TaskExecutor};
|
||||
|
||||
// Stuck on tokio 0.1 until the jsonrpc-pubsub crate upgrades to tokio 0.2
|
||||
use tokio_01::runtime::{Builder as RuntimeBuilder, Runtime, TaskExecutor};
|
||||
|
||||
const RECEIVE_DELAY_MILLIS: u64 = 100;
|
||||
|
||||
@@ -87,29 +94,44 @@ impl std::fmt::Debug for NotificationEntry {
|
||||
}
|
||||
}
|
||||
|
||||
struct SubscriptionData<S> {
|
||||
struct SubscriptionData<S, T> {
|
||||
sink: Sink<S>,
|
||||
commitment: CommitmentConfig,
|
||||
last_notified_slot: RwLock<Slot>,
|
||||
config: Option<T>,
|
||||
}
|
||||
type RpcAccountSubscriptions =
|
||||
RwLock<HashMap<Pubkey, HashMap<SubscriptionId, SubscriptionData<Response<UiAccount>>>>>;
|
||||
type RpcProgramSubscriptions =
|
||||
RwLock<HashMap<Pubkey, HashMap<SubscriptionId, SubscriptionData<Response<RpcKeyedAccount>>>>>;
|
||||
#[derive(Default, Clone)]
|
||||
struct ProgramConfig {
|
||||
filters: Vec<RpcFilterType>,
|
||||
encoding: Option<UiAccountEncoding>,
|
||||
}
|
||||
type RpcAccountSubscriptions = RwLock<
|
||||
HashMap<
|
||||
Pubkey,
|
||||
HashMap<SubscriptionId, SubscriptionData<Response<UiAccount>, UiAccountEncoding>>,
|
||||
>,
|
||||
>;
|
||||
type RpcProgramSubscriptions = RwLock<
|
||||
HashMap<
|
||||
Pubkey,
|
||||
HashMap<SubscriptionId, SubscriptionData<Response<RpcKeyedAccount>, ProgramConfig>>,
|
||||
>,
|
||||
>;
|
||||
type RpcSignatureSubscriptions = RwLock<
|
||||
HashMap<Signature, HashMap<SubscriptionId, SubscriptionData<Response<RpcSignatureResult>>>>,
|
||||
HashMap<Signature, HashMap<SubscriptionId, SubscriptionData<Response<RpcSignatureResult>, ()>>>,
|
||||
>;
|
||||
type RpcSlotSubscriptions = RwLock<HashMap<SubscriptionId, Sink<SlotInfo>>>;
|
||||
type RpcVoteSubscriptions = RwLock<HashMap<SubscriptionId, Sink<RpcVote>>>;
|
||||
type RpcRootSubscriptions = RwLock<HashMap<SubscriptionId, Sink<Slot>>>;
|
||||
|
||||
fn add_subscription<K, S>(
|
||||
subscriptions: &mut HashMap<K, HashMap<SubscriptionId, SubscriptionData<S>>>,
|
||||
fn add_subscription<K, S, T>(
|
||||
subscriptions: &mut HashMap<K, HashMap<SubscriptionId, SubscriptionData<S, T>>>,
|
||||
hashmap_key: K,
|
||||
commitment: Option<CommitmentConfig>,
|
||||
sub_id: SubscriptionId,
|
||||
subscriber: Subscriber<S>,
|
||||
last_notified_slot: Slot,
|
||||
config: Option<T>,
|
||||
) where
|
||||
K: Eq + Hash,
|
||||
S: Clone,
|
||||
@@ -120,6 +142,7 @@ fn add_subscription<K, S>(
|
||||
sink,
|
||||
commitment,
|
||||
last_notified_slot: RwLock::new(last_notified_slot),
|
||||
config,
|
||||
};
|
||||
if let Some(current_hashmap) = subscriptions.get_mut(&hashmap_key) {
|
||||
current_hashmap.insert(sub_id, subscription_data);
|
||||
@@ -130,8 +153,8 @@ fn add_subscription<K, S>(
|
||||
subscriptions.insert(hashmap_key, hashmap);
|
||||
}
|
||||
|
||||
fn remove_subscription<K, S>(
|
||||
subscriptions: &mut HashMap<K, HashMap<SubscriptionId, SubscriptionData<S>>>,
|
||||
fn remove_subscription<K, S, T>(
|
||||
subscriptions: &mut HashMap<K, HashMap<SubscriptionId, SubscriptionData<S, T>>>,
|
||||
sub_id: &SubscriptionId,
|
||||
) -> bool
|
||||
where
|
||||
@@ -153,8 +176,8 @@ where
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn check_commitment_and_notify<K, S, B, F, X>(
|
||||
subscriptions: &HashMap<K, HashMap<SubscriptionId, SubscriptionData<Response<S>>>>,
|
||||
fn check_commitment_and_notify<K, S, B, F, X, T>(
|
||||
subscriptions: &HashMap<K, HashMap<SubscriptionId, SubscriptionData<Response<S>, T>>>,
|
||||
hashmap_key: &K,
|
||||
bank_forks: &Arc<RwLock<BankForks>>,
|
||||
cache_slot_info: &CacheSlotInfo,
|
||||
@@ -166,8 +189,9 @@ where
|
||||
K: Eq + Hash + Clone + Copy,
|
||||
S: Clone + Serialize,
|
||||
B: Fn(&Bank, &K) -> X,
|
||||
F: Fn(X, Slot) -> (Box<dyn Iterator<Item = S>>, Slot),
|
||||
F: Fn(X, &K, Slot, Option<T>, Option<Arc<Bank>>) -> (Box<dyn Iterator<Item = S>>, Slot),
|
||||
X: Clone + Serialize + Default,
|
||||
T: Clone,
|
||||
{
|
||||
let mut notified_set: HashSet<SubscriptionId> = HashSet::new();
|
||||
if let Some(hashmap) = subscriptions.get(hashmap_key) {
|
||||
@@ -177,6 +201,7 @@ where
|
||||
sink,
|
||||
commitment,
|
||||
last_notified_slot,
|
||||
config,
|
||||
},
|
||||
) in hashmap.iter()
|
||||
{
|
||||
@@ -188,15 +213,19 @@ where
|
||||
cache_slot_info.highest_confirmed_slot
|
||||
}
|
||||
};
|
||||
let results = {
|
||||
let bank_forks = bank_forks.read().unwrap();
|
||||
bank_forks
|
||||
.get(slot)
|
||||
.map(|desired_bank| bank_method(&desired_bank, hashmap_key))
|
||||
.unwrap_or_default()
|
||||
};
|
||||
let bank = bank_forks.read().unwrap().get(slot).cloned();
|
||||
let results = bank
|
||||
.clone()
|
||||
.map(|desired_bank| bank_method(&desired_bank, hashmap_key))
|
||||
.unwrap_or_default();
|
||||
let mut w_last_notified_slot = last_notified_slot.write().unwrap();
|
||||
let (filter_results, result_slot) = filter_results(results, *w_last_notified_slot);
|
||||
let (filter_results, result_slot) = filter_results(
|
||||
results,
|
||||
hashmap_key,
|
||||
*w_last_notified_slot,
|
||||
config.as_ref().cloned(),
|
||||
bank,
|
||||
);
|
||||
for result in filter_results {
|
||||
notifier.notify(
|
||||
Response {
|
||||
@@ -227,19 +256,29 @@ impl RpcNotifier {
|
||||
|
||||
fn filter_account_result(
|
||||
result: Option<(Account, Slot)>,
|
||||
pubkey: &Pubkey,
|
||||
last_notified_slot: Slot,
|
||||
encoding: Option<UiAccountEncoding>,
|
||||
bank: Option<Arc<Bank>>,
|
||||
) -> (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(UiAccount::encode(
|
||||
account,
|
||||
UiAccountEncoding::Binary,
|
||||
))),
|
||||
fork,
|
||||
);
|
||||
let encoding = encoding.unwrap_or(UiAccountEncoding::Binary);
|
||||
if account.owner == spl_token_id_v1_0() && encoding == UiAccountEncoding::JsonParsed {
|
||||
let bank = bank.unwrap(); // If result.is_some(), bank must also be Some
|
||||
if let Some(ui_account) = get_parsed_token_account(bank, pubkey, account) {
|
||||
return (Box::new(iter::once(ui_account)), fork);
|
||||
}
|
||||
} else {
|
||||
return (
|
||||
Box::new(iter::once(UiAccount::encode(
|
||||
pubkey, account, encoding, None,
|
||||
))),
|
||||
fork,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
(Box::new(iter::empty()), last_notified_slot)
|
||||
@@ -247,7 +286,10 @@ fn filter_account_result(
|
||||
|
||||
fn filter_signature_result(
|
||||
result: Option<transaction::Result<()>>,
|
||||
_signature: &Signature,
|
||||
last_notified_slot: Slot,
|
||||
_config: Option<()>,
|
||||
_bank: Option<Arc<Bank>>,
|
||||
) -> (Box<dyn Iterator<Item = RpcSignatureResult>>, Slot) {
|
||||
(
|
||||
Box::new(
|
||||
@@ -261,19 +303,33 @@ fn filter_signature_result(
|
||||
|
||||
fn filter_program_results(
|
||||
accounts: Vec<(Pubkey, Account)>,
|
||||
_program_id: &Pubkey,
|
||||
last_notified_slot: Slot,
|
||||
config: Option<ProgramConfig>,
|
||||
bank: Option<Arc<Bank>>,
|
||||
) -> (Box<dyn Iterator<Item = RpcKeyedAccount>>, Slot) {
|
||||
(
|
||||
Box::new(
|
||||
accounts
|
||||
.into_iter()
|
||||
.map(|(pubkey, account)| RpcKeyedAccount {
|
||||
let config = config.unwrap_or_default();
|
||||
let encoding = config.encoding.unwrap_or(UiAccountEncoding::Binary);
|
||||
let filters = config.filters;
|
||||
let keyed_accounts = accounts.into_iter().filter(move |(_, account)| {
|
||||
filters.iter().all(|filter_type| match filter_type {
|
||||
RpcFilterType::DataSize(size) => account.data.len() as u64 == *size,
|
||||
RpcFilterType::Memcmp(compare) => compare.bytes_match(&account.data),
|
||||
})
|
||||
});
|
||||
let accounts: Box<dyn Iterator<Item = RpcKeyedAccount>> =
|
||||
if encoding == UiAccountEncoding::JsonParsed {
|
||||
let bank = bank.unwrap(); // If !accounts.is_empty(), bank must be Some
|
||||
Box::new(get_parsed_token_accounts(bank, keyed_accounts))
|
||||
} else {
|
||||
Box::new(
|
||||
keyed_accounts.map(move |(pubkey, account)| RpcKeyedAccount {
|
||||
pubkey: pubkey.to_string(),
|
||||
account: UiAccount::encode(account, UiAccountEncoding::Binary),
|
||||
account: UiAccount::encode(&pubkey, account, encoding.clone(), None),
|
||||
}),
|
||||
),
|
||||
last_notified_slot,
|
||||
)
|
||||
)
|
||||
};
|
||||
(accounts, last_notified_slot)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -461,11 +517,13 @@ impl RpcSubscriptions {
|
||||
pub fn add_account_subscription(
|
||||
&self,
|
||||
pubkey: Pubkey,
|
||||
commitment: Option<CommitmentConfig>,
|
||||
config: Option<RpcAccountInfoConfig>,
|
||||
sub_id: SubscriptionId,
|
||||
subscriber: Subscriber<Response<UiAccount>>,
|
||||
) {
|
||||
let commitment_level = commitment
|
||||
let config = config.unwrap_or_default();
|
||||
let commitment_level = config
|
||||
.commitment
|
||||
.unwrap_or_else(CommitmentConfig::single)
|
||||
.commitment;
|
||||
let slot = match commitment_level {
|
||||
@@ -511,10 +569,11 @@ impl RpcSubscriptions {
|
||||
add_subscription(
|
||||
&mut subscriptions,
|
||||
pubkey,
|
||||
commitment,
|
||||
config.commitment,
|
||||
sub_id,
|
||||
subscriber,
|
||||
last_notified_slot,
|
||||
config.encoding,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -535,11 +594,14 @@ impl RpcSubscriptions {
|
||||
pub fn add_program_subscription(
|
||||
&self,
|
||||
program_id: Pubkey,
|
||||
commitment: Option<CommitmentConfig>,
|
||||
config: Option<RpcProgramAccountsConfig>,
|
||||
sub_id: SubscriptionId,
|
||||
subscriber: Subscriber<Response<RpcKeyedAccount>>,
|
||||
) {
|
||||
let commitment_level = commitment
|
||||
let config = config.unwrap_or_default();
|
||||
let commitment_level = config
|
||||
.account_config
|
||||
.commitment
|
||||
.unwrap_or_else(CommitmentConfig::recent)
|
||||
.commitment;
|
||||
let mut subscriptions = if commitment_level == CommitmentLevel::SingleGossip {
|
||||
@@ -553,10 +615,14 @@ impl RpcSubscriptions {
|
||||
add_subscription(
|
||||
&mut subscriptions,
|
||||
program_id,
|
||||
commitment,
|
||||
config.account_config.commitment,
|
||||
sub_id,
|
||||
subscriber,
|
||||
0, // last_notified_slot is not utilized for program subscriptions
|
||||
Some(ProgramConfig {
|
||||
filters: config.filters.unwrap_or_default(),
|
||||
encoding: config.account_config.encoding,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -599,6 +665,7 @@ impl RpcSubscriptions {
|
||||
sub_id,
|
||||
subscriber,
|
||||
0, // last_notified_slot is not utilized for signature subscriptions
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -829,7 +896,7 @@ impl RpcSubscriptions {
|
||||
&subscriptions.gossip_account_subscriptions,
|
||||
&subscriptions.gossip_program_subscriptions,
|
||||
&subscriptions.gossip_signature_subscriptions,
|
||||
&bank_forks,
|
||||
bank_forks,
|
||||
&cache_slot_info,
|
||||
¬ifier,
|
||||
);
|
||||
@@ -850,7 +917,7 @@ impl RpcSubscriptions {
|
||||
for pubkey in &pubkeys {
|
||||
Self::check_account(
|
||||
pubkey,
|
||||
&bank_forks,
|
||||
bank_forks,
|
||||
account_subscriptions.clone(),
|
||||
¬ifier,
|
||||
&cache_slot_info,
|
||||
@@ -864,7 +931,7 @@ impl RpcSubscriptions {
|
||||
for program_id in &programs {
|
||||
Self::check_program(
|
||||
program_id,
|
||||
&bank_forks,
|
||||
bank_forks,
|
||||
program_subscriptions.clone(),
|
||||
¬ifier,
|
||||
&cache_slot_info,
|
||||
@@ -878,7 +945,7 @@ impl RpcSubscriptions {
|
||||
for signature in &signatures {
|
||||
Self::check_signature(
|
||||
signature,
|
||||
&bank_forks,
|
||||
bank_forks,
|
||||
signature_subscriptions.clone(),
|
||||
¬ifier,
|
||||
&cache_slot_info,
|
||||
@@ -923,7 +990,7 @@ pub(crate) mod tests {
|
||||
system_transaction,
|
||||
};
|
||||
use std::{fmt::Debug, sync::mpsc::channel, time::Instant};
|
||||
use tokio::{prelude::FutureExt, runtime::Runtime, timer::Delay};
|
||||
use tokio_01::{prelude::FutureExt, runtime::Runtime, timer::Delay};
|
||||
|
||||
pub(crate) fn robust_poll_or_panic<T: Debug + Send + 'static>(
|
||||
receiver: futures::sync::mpsc::Receiver<T>,
|
||||
@@ -985,7 +1052,10 @@ pub(crate) mod tests {
|
||||
);
|
||||
subscriptions.add_account_subscription(
|
||||
alice.pubkey(),
|
||||
Some(CommitmentConfig::recent()),
|
||||
Some(RpcAccountInfoConfig {
|
||||
commitment: Some(CommitmentConfig::recent()),
|
||||
encoding: None,
|
||||
}),
|
||||
sub_id.clone(),
|
||||
subscriber,
|
||||
);
|
||||
@@ -1395,7 +1465,7 @@ pub(crate) mod tests {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_add_and_remove_subscription() {
|
||||
let mut subscriptions: HashMap<u64, HashMap<SubscriptionId, SubscriptionData<()>>> =
|
||||
let mut subscriptions: HashMap<u64, HashMap<SubscriptionId, SubscriptionData<(), ()>>> =
|
||||
HashMap::new();
|
||||
|
||||
let num_keys = 5;
|
||||
@@ -1403,7 +1473,7 @@ pub(crate) mod tests {
|
||||
let (subscriber, _id_receiver, _transport_receiver) =
|
||||
Subscriber::new_test("notification");
|
||||
let sub_id = SubscriptionId::Number(key);
|
||||
add_subscription(&mut subscriptions, key, None, sub_id, subscriber, 0);
|
||||
add_subscription(&mut subscriptions, key, None, sub_id, subscriber, 0, None);
|
||||
}
|
||||
|
||||
// Add another subscription to the "0" key
|
||||
@@ -1416,6 +1486,7 @@ pub(crate) mod tests {
|
||||
extra_sub_id.clone(),
|
||||
subscriber,
|
||||
0,
|
||||
None,
|
||||
);
|
||||
|
||||
assert_eq!(subscriptions.len(), num_keys as usize);
|
||||
@@ -1477,7 +1548,10 @@ pub(crate) mod tests {
|
||||
let sub_id0 = SubscriptionId::Number(0 as u64);
|
||||
subscriptions.add_account_subscription(
|
||||
alice.pubkey(),
|
||||
Some(CommitmentConfig::single_gossip()),
|
||||
Some(RpcAccountInfoConfig {
|
||||
commitment: Some(CommitmentConfig::single_gossip()),
|
||||
encoding: None,
|
||||
}),
|
||||
sub_id0.clone(),
|
||||
subscriber0,
|
||||
);
|
||||
@@ -1542,7 +1616,10 @@ pub(crate) mod tests {
|
||||
let sub_id1 = SubscriptionId::Number(1 as u64);
|
||||
subscriptions.add_account_subscription(
|
||||
alice.pubkey(),
|
||||
Some(CommitmentConfig::single_gossip()),
|
||||
Some(RpcAccountInfoConfig {
|
||||
commitment: Some(CommitmentConfig::single_gossip()),
|
||||
encoding: None,
|
||||
}),
|
||||
sub_id1.clone(),
|
||||
subscriber1,
|
||||
);
|
||||
|
@@ -26,7 +26,7 @@ use std::{
|
||||
thread::sleep,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use tokio::runtime::Runtime;
|
||||
use tokio_01::runtime::Runtime;
|
||||
|
||||
macro_rules! json_req {
|
||||
($method: expr, $params: expr) => {{
|
||||
@@ -100,6 +100,19 @@ fn test_rpc_send_tx() {
|
||||
|
||||
assert_eq!(confirmed_tx, true);
|
||||
|
||||
use solana_account_decoder::UiAccountEncoding;
|
||||
use solana_client::rpc_config::RpcAccountInfoConfig;
|
||||
let config = RpcAccountInfoConfig {
|
||||
encoding: Some(UiAccountEncoding::Binary64),
|
||||
commitment: None,
|
||||
};
|
||||
let req = json_req!(
|
||||
"getAccountInfo",
|
||||
json!([bs58::encode(bob_pubkey).into_string(), config])
|
||||
);
|
||||
let json: Value = post_rpc(req, &leader_data);
|
||||
info!("{:?}", json["result"]["value"]);
|
||||
|
||||
server.close().unwrap();
|
||||
remove_dir_all(ledger_path).unwrap();
|
||||
}
|
||||
@@ -189,7 +202,7 @@ fn test_rpc_subscriptions() {
|
||||
.and_then(move |client| {
|
||||
for sig in signature_set {
|
||||
let status_sender = status_sender.clone();
|
||||
tokio::spawn(
|
||||
tokio_01::spawn(
|
||||
client
|
||||
.signature_subscribe(sig.clone(), None)
|
||||
.and_then(move |sig_stream| {
|
||||
@@ -203,7 +216,7 @@ fn test_rpc_subscriptions() {
|
||||
}),
|
||||
);
|
||||
}
|
||||
tokio::spawn(
|
||||
tokio_01::spawn(
|
||||
client
|
||||
.slot_subscribe()
|
||||
.and_then(move |slot_stream| {
|
||||
@@ -218,7 +231,7 @@ fn test_rpc_subscriptions() {
|
||||
);
|
||||
for pubkey in account_set {
|
||||
let account_sender = account_sender.clone();
|
||||
tokio::spawn(
|
||||
tokio_01::spawn(
|
||||
client
|
||||
.account_subscribe(pubkey, None)
|
||||
.and_then(move |account_stream| {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-crate-features"
|
||||
version = "1.2.12"
|
||||
version = "1.2.21"
|
||||
description = "Solana Crate Features"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@@ -21,7 +21,7 @@ rand_chacha = { version = "0.2.2" }
|
||||
regex-syntax = { version = "0.6.12" }
|
||||
reqwest = { version = "0.10.1", default-features = false, features = ["blocking", "rustls-tls", "json"] }
|
||||
serde = { version = "1.0.100", features = ["rc"] }
|
||||
ed25519-dalek = { version = "=1.0.0-pre.3", features = ["serde"] }
|
||||
ed25519-dalek = { version = "=1.0.0-pre.4", features = ["serde"] }
|
||||
syn_0_15 = { package = "syn", version = "0.15.42", features = ["extra-traits", "fold", "full"] }
|
||||
syn_1_0 = { package = "syn", version = "1.0.3", features = ["extra-traits", "fold", "full"] }
|
||||
tokio = { version = "0.1.22",features=["bytes", "codec", "default", "fs", "io", "mio", "num_cpus", "reactor", "rt-full", "sync", "tcp", "timer", "tokio-codec", "tokio-current-thread", "tokio-executor", "tokio-io", "tokio-io", "tokio-reactor", "tokio-tcp", "tokio-tcp", "tokio-threadpool", "tokio-timer", "tokio-udp", "tokio-uds", "udp", "uds"] }
|
||||
|
1
docs/.gitignore
vendored
1
docs/.gitignore
vendored
@@ -11,7 +11,6 @@
|
||||
/static/img/*.svg
|
||||
/static/img/*.png
|
||||
vercel.json
|
||||
package-lock.json
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
|
@@ -17,7 +17,7 @@ module.exports = {
|
||||
links: [
|
||||
{
|
||||
to: "introduction",
|
||||
label: "Docs",
|
||||
label: "Introduction",
|
||||
position: "left",
|
||||
},
|
||||
{
|
||||
@@ -30,6 +30,11 @@ module.exports = {
|
||||
label: "Validators",
|
||||
position: "left",
|
||||
},
|
||||
{
|
||||
to: "clusters",
|
||||
label: "Clusters",
|
||||
position: "left",
|
||||
},
|
||||
{
|
||||
href: "https://discordapp.com/invite/pquxPsq",
|
||||
label: "Chat",
|
||||
@@ -43,6 +48,10 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
},
|
||||
algolia: {
|
||||
apiKey: "d58e0d68c875346d52645d68b13f3ac0",
|
||||
indexName: "solana",
|
||||
},
|
||||
footer: {
|
||||
style: "dark",
|
||||
links: [
|
||||
|
13659
docs/package-lock.json
generated
Normal file
13659
docs/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -38,9 +38,11 @@ module.exports = {
|
||||
"cli/conventions",
|
||||
"cli/choose-a-cluster",
|
||||
"cli/transfer-tokens",
|
||||
"cli/delegate-stake",
|
||||
"cli/manage-stake-accounts",
|
||||
"offline-signing",
|
||||
"offline-signing/durable-nonce",
|
||||
"cli/usage",
|
||||
],
|
||||
"Solana Clusters": ["clusters"],
|
||||
"Develop Applications": [
|
||||
@@ -60,6 +62,7 @@ module.exports = {
|
||||
"running-validator",
|
||||
"running-validator/validator-reqs",
|
||||
"running-validator/validator-start",
|
||||
"running-validator/vote-accounts",
|
||||
"running-validator/validator-stake",
|
||||
"running-validator/validator-monitor",
|
||||
"running-validator/validator-info",
|
||||
@@ -143,7 +146,6 @@ module.exports = {
|
||||
"implemented-proposals/repair-service",
|
||||
"implemented-proposals/testing-programs",
|
||||
"implemented-proposals/readonly-accounts",
|
||||
"implemented-proposals/embedding-move",
|
||||
"implemented-proposals/staking-rewards",
|
||||
"implemented-proposals/rent",
|
||||
"implemented-proposals/durable-tx-nonces",
|
||||
@@ -169,7 +171,6 @@ module.exports = {
|
||||
"proposals/block-confirmation",
|
||||
"proposals/rust-clients",
|
||||
"proposals/optimistic_confirmation",
|
||||
"proposals/abi-management",
|
||||
],
|
||||
},
|
||||
};
|
||||
|
@@ -24,6 +24,7 @@ To interact with a Solana node inside a JavaScript application, use the [solana-
|
||||
- [getConfirmedBlock](jsonrpc-api.md#getconfirmedblock)
|
||||
- [getConfirmedBlocks](jsonrpc-api.md#getconfirmedblocks)
|
||||
- [getConfirmedSignaturesForAddress](jsonrpc-api.md#getconfirmedsignaturesforaddress)
|
||||
- [getConfirmedSignaturesForAddress2](jsonrpc-api.md#getconfirmedsignaturesforaddress2)
|
||||
- [getConfirmedTransaction](jsonrpc-api.md#getconfirmedtransaction)
|
||||
- [getEpochInfo](jsonrpc-api.md#getepochinfo)
|
||||
- [getEpochSchedule](jsonrpc-api.md#getepochschedule)
|
||||
@@ -64,6 +65,15 @@ To interact with a Solana node inside a JavaScript application, use the [solana-
|
||||
- [slotSubscribe](jsonrpc-api.md#slotsubscribe)
|
||||
- [slotUnsubscribe](jsonrpc-api.md#slotunsubscribe)
|
||||
|
||||
## Unstable Methods
|
||||
|
||||
Unstable methods may see breaking changes in patch releases and may not be supported in perpetuity.
|
||||
|
||||
- [getTokenAccountBalance](jsonrpc-api.md#gettokenaccountbalance)
|
||||
- [getTokenAccountsByDelegate](jsonrpc-api.md#gettokenaccountsbydelegate)
|
||||
- [getTokenAccountsByOwner](jsonrpc-api.md#gettokenaccountsbyowner)
|
||||
- [getTokenSupply](jsonrpc-api.md#gettokensupply)
|
||||
|
||||
## Request Formatting
|
||||
|
||||
To make a JSON-RPC request, send an HTTP POST request with a `Content-Type: application/json` header. The JSON request data should contain 4 fields:
|
||||
@@ -146,8 +156,8 @@ Returns all information associated with the account of provided Pubkey
|
||||
- `<string>` - Pubkey of account to query, as base-58 encoded string
|
||||
- `<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>`.
|
||||
- (optional) `encoding: <string>` - encoding for Account data, either "binary", "binary64", or jsonParsed". If parameter not provided, the default encoding is "binary". "binary" is base-58 encoded and limited to Account data of less than 128 bytes. "binary64" will return base64 encoded data for Account data of any size.
|
||||
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>`. **jsonParsed encoding is UNSTABLE**
|
||||
|
||||
#### Results:
|
||||
|
||||
@@ -295,7 +305,7 @@ 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". 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. **jsonParsed encoding is UNSTABLE**
|
||||
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:
|
||||
@@ -385,6 +395,8 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id":1,"m
|
||||
|
||||
### getConfirmedSignaturesForAddress
|
||||
|
||||
**DEPRECATED: Please use getConfirmedSignaturesForAddress2 instead**
|
||||
|
||||
Returns a list of all the confirmed signatures for transactions involving an
|
||||
address, within a specified Slot range. Max range allowed is 10,000 Slots
|
||||
|
||||
@@ -412,6 +424,37 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id":1,"m
|
||||
{"jsonrpc":"2.0","result":{["35YGay1Lwjwgxe9zaH6APSHbt9gYQUCtBWTNL3aVwVGn9xTFw2fgds7qK5AL29mP63A9j3rh8KpN1TgSR62XCaby","4bJdGN8Tt2kLWZ3Fa1dpwPSEkXWWTSszPSf1rRVsCwNjxbbUdwTeiWtmi8soA26YmwnKD4aAxNp8ci1Gjpdv4gsr","4LQ14a7BYY27578Uj8LPCaVhSdJGLn9DJqnUJHpy95FMqdKf9acAhUhecPQNjNUy6VoNFUbvwYkPociFSf87cWbG"]},"id":1}
|
||||
```
|
||||
|
||||
|
||||
### getConfirmedSignaturesForAddress2
|
||||
|
||||
Returns confirmed signatures for transactions involving an
|
||||
address backwards in time from the provided signature or most recent confirmed block
|
||||
|
||||
#### Parameters:
|
||||
* `<string>` - account address as base-58 encoded string
|
||||
* `<object>` - (optional) Configuration object containing the following fields:
|
||||
* `before: <string>` - (optional) start searching backwards from this transaction signature.
|
||||
If not provided the search starts from the top of the highest max confirmed block.
|
||||
* `limit: <number>` - (optional) maximum transaction signatures to return (between 1 and 1,000, default: 1,000).
|
||||
|
||||
#### Results:
|
||||
The result field will be an array of transaction signature information, ordered
|
||||
from newest to oldest transaction:
|
||||
* `<object>`
|
||||
* `signature: <string>` - transaction signature as base-58 encoded string
|
||||
* `slot: <u64>` - The slot that contains the block with the transaction
|
||||
* `err: <object | null>` - Error if transaction failed, null if transaction succeeded. [TransactionError definitions](https://github.com/solana-labs/solana/blob/master/sdk/src/transaction.rs#L14)
|
||||
* `memo: <string |null>` - Memo associated with the transaction, null if no memo is present
|
||||
|
||||
#### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id":1,"method":"getConfirmedSignaturesForAddress2","params":["Vote111111111111111111111111111111111111111", {"limit": 1}]}' localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":[{"err":null,"memo":null,"signature":"5h6xBEauJ3PK6SWCZ1PGjBvj8vDdWG3KpwATGy1ARAXFSDwt8GFXM7W5Ncn16wmqokgpiKRLuS83KUxyZyv2sUYv","slot":114}],"id":1}
|
||||
```
|
||||
|
||||
### getConfirmedTransaction
|
||||
|
||||
Returns transaction details for a confirmed transaction
|
||||
@@ -420,7 +463,7 @@ Returns transaction details for a confirmed transaction
|
||||
|
||||
- `<string>` - transaction signature as base-58 encoded string
|
||||
N 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).
|
||||
- `<string>` - (optional) encoding for the returned Transaction, either "json", "jsonParsed", or "binary".
|
||||
- `<string>` - (optional) encoding for the returned Transaction, either "json", "jsonParsed", or "binary". **jsonParsed encoding is UNSTABLE**
|
||||
|
||||
#### Results:
|
||||
|
||||
@@ -801,7 +844,7 @@ Returns all accounts owned by the provided program Pubkey
|
||||
- `<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>`.
|
||||
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>`. **jsonParsed encoding is UNSTABLE**
|
||||
- (optional) `filters: <array>` - filter results using various [filter objects](jsonrpc-api.md#filters); account must meet all filter criteria to be included in results
|
||||
|
||||
##### Filters:
|
||||
@@ -1016,6 +1059,130 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "
|
||||
{"jsonrpc":"2.0","result":{"context":{"slot":1114},"value":{"circulating":16000,"nonCirculating":1000000,"nonCirculatingAccounts":["FEy8pTbP5fEoqMV1GdTz83byuA8EKByqYat1PKDgVAq5","9huDUZfxoJ7wGMTffUE7vh1xePqef7gyrLJu9NApncqA","3mi1GmwEE3zo2jmfDuzvjSX9ovRXsDUKHvsntpkhuLJ9","BYxEJTDerkaRWBem3XgnVcdhppktBXa2HbkHPKj2Ui4Z],total:1016000}},"id":1}
|
||||
```
|
||||
|
||||
### getTokenAccountBalance
|
||||
|
||||
Returns the token balance of an SPL Token account. **UNSTABLE**
|
||||
|
||||
#### Parameters:
|
||||
|
||||
- `<string>` - Pubkey of Token account to query, as base-58 encoded string
|
||||
- `<object>` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
|
||||
|
||||
#### Results:
|
||||
|
||||
The result will be an RpcResponse JSON object with `value` equal to a JSON object containing:
|
||||
|
||||
- `uiAmount: <f64>` - the balance, using mint-prescribed decimals
|
||||
- `amount: <string>` - the raw balance without decimals, a string representation of u64
|
||||
- `decimals: <u8>` - number of base 10 digits to the right of the decimal place
|
||||
|
||||
#### Example:
|
||||
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getTokenAccountBalance", "params": ["7fUAJdStEuGbc3sM84cKRL6yYaaSstyLSU4ve5oovLS7"]}' http://localhost:8899
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":{"context":{"slot":1114},"value":{"uiAmount":98.64,"amount":"9864","decimals":2},"id":1}
|
||||
```
|
||||
|
||||
### getTokenAccountsByDelegate
|
||||
|
||||
Returns all SPL Token accounts by approved Delegate. **UNSTABLE**
|
||||
|
||||
#### Parameters:
|
||||
|
||||
- `<string>` - Pubkey of account delegate to query, as base-58 encoded string
|
||||
- `<object>` - Either:
|
||||
* `mint: <string>` - Pubkey of the specific token Mint to limit accounts to, as base-58 encoded string; or
|
||||
* `programId: <string>` - Pubkey of the Token program ID that owns the accounts, as base-58 encoded string
|
||||
- `<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>`. **jsonParsed encoding is UNSTABLE**
|
||||
|
||||
#### Results:
|
||||
|
||||
The result will be an RpcResponse JSON object with `value` equal to an array of JSON objects, which will contain:
|
||||
|
||||
- `pubkey: <string>` - the account Pubkey as base-58 encoded string
|
||||
- `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: <object>`, Token state data associated with the account, either as base-58 encoded binary data or in JSON format `{<program>: <state>}`
|
||||
- `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
|
||||
|
||||
#### Example:
|
||||
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getTokenAccountsByDelegate", "params": ["4Nd1mBQtrMJVYVfKf2PJy9NZUZdTAsp7D4xWLs4gDB4T", {"programId": "TokenSVp5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o"}, {"encoding": "jsonParsed"}]}' http://localhost:8899
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":{"context":{"slot":1114},"value":[{"data":{"program":"spl-token","parsed":{"accountType":"account","info":{"tokenAmount":{"amount":"1","uiAmount":0.1,"decimals":1},"delegate":"4Nd1mBQtrMJVYVfKf2PJy9NZUZdTAsp7D4xWLs4gDB4T","delegatedAmount":1,"isInitialized":true,"isNative":false,"mint":"3wyAj7Rt1TWVPZVteFJPLa26JmLvdb1CAKEFZm3NY75E","owner":"CnPoSPKXu7wJqxe59Fs72tkBeALovhsCxYeFwPCQH9TD"}}},"executable":false,"lamports":1726080,"owner":"TokenSVp5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o","rentEpoch":4},"pubkey":"CnPoSPKXu7wJqxe59Fs72tkBeALovhsCxYeFwPCQH9TD"}],"id":1}
|
||||
```
|
||||
|
||||
### getTokenAccountsByOwner
|
||||
|
||||
Returns all SPL Token accounts by token owner. **UNSTABLE**
|
||||
|
||||
#### Parameters:
|
||||
|
||||
- `<string>` - Pubkey of account owner to query, as base-58 encoded string
|
||||
- `<object>` - Either:
|
||||
* `mint: <string>` - Pubkey of the specific token Mint to limit accounts to, as base-58 encoded string; or
|
||||
* `programId: <string>` - Pubkey of the Token program ID that owns the accounts, as base-58 encoded string
|
||||
- `<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>`. **jsonParsed encoding is UNSTABLE**
|
||||
|
||||
#### Results:
|
||||
|
||||
The result will be an RpcResponse JSON object with `value` equal to an array of JSON objects, which will contain:
|
||||
|
||||
- `pubkey: <string>` - the account Pubkey as base-58 encoded string
|
||||
- `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: <object>`, Token state data associated with the account, either as base-58 encoded binary data or in JSON format `{<program>: <state>}`
|
||||
- `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
|
||||
|
||||
#### Example:
|
||||
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getTokenAccountsByOwner", "params": ["4Qkev8aNZcqFNSRhQzwyLMFSsi94jHqE8WNVTJzTP99F", {"mint":"3wyAj7Rt1TWVPZVteFJPLa26JmLvdb1CAKEFZm3NY75E"}, {"encoding": "jsonParsed"}]}' http://localhost:8899
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":{"context":{"slot":1114},"value":[{"data":{"program":"spl-token","parsed":{"accountType":"account","info":{"tokenAmount":{"amount":"1","uiAmount":0.1,"decimals":1},"delegate":null,"delegatedAmount":1,"isInitialized":true,"isNative":false,"mint":"3wyAj7Rt1TWVPZVteFJPLa26JmLvdb1CAKEFZm3NY75E","owner":"4Qkev8aNZcqFNSRhQzwyLMFSsi94jHqE8WNVTJzTP99F"}}},"executable":false,"lamports":1726080,"owner":"TokenSVp5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o","rentEpoch":4},"pubkey":"CnPoSPKXu7wJqxe59Fs72tkBeALovhsCxYeFwPCQH9TD"}],"id":1}
|
||||
```
|
||||
|
||||
### getTokenSupply
|
||||
|
||||
Returns the total supply of an SPL Token type. **UNSTABLE**
|
||||
|
||||
#### Parameters:
|
||||
|
||||
- `<string>` - Pubkey of token Mint to query, as base-58 encoded string
|
||||
- `<object>` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
|
||||
|
||||
#### Results:
|
||||
|
||||
The result will be an RpcResponse JSON object with `value` equal to a JSON object containing:
|
||||
|
||||
- `uiAmount: <f64>` - the total token supply, using mint-prescribed decimals
|
||||
- `amount: <string>` - the raw total token supply without decimals, a string representation of u64
|
||||
- `decimals: <u8>` - number of base 10 digits to the right of the decimal place
|
||||
|
||||
#### Example:
|
||||
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getTokenSupply", "params": ["3wyAj7Rt1TWVPZVteFJPLa26JmLvdb1CAKEFZm3NY75E"]}' http://localhost:8899
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":{"context":{"slot":1114},"value":{"uiAmount":1000.0,"amount":"100000","decimals":2},"id":1}
|
||||
```
|
||||
|
||||
### getTransactionCount
|
||||
|
||||
Returns the current Transaction count from the ledger
|
||||
@@ -1058,7 +1225,7 @@ The result field will be a JSON object with the following fields:
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getVersion"}' http://localhost:8899
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":{"solana-core": "1.2.12"},"id":1}
|
||||
{"jsonrpc":"2.0","result":{"solana-core": "1.2.21"},"id":1}
|
||||
```
|
||||
|
||||
### getVoteAccounts
|
||||
@@ -1256,7 +1423,10 @@ Subscribe to an account to receive notifications when the lamports or data for a
|
||||
#### Parameters:
|
||||
|
||||
- `<string>` - account Pubkey, as base-58 encoded string
|
||||
- `<object>` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
|
||||
- `<object>` - (optional) Configuration object containing the following optional fields:
|
||||
- `<object>` - (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>`. **jsonParsed encoding is UNSTABLE**
|
||||
|
||||
#### Results:
|
||||
|
||||
@@ -1270,13 +1440,16 @@ Subscribe to an account to receive notifications when the lamports or data for a
|
||||
|
||||
{"jsonrpc":"2.0", "id":1, "method":"accountSubscribe", "params":["CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12", {"commitment": "single"}]}
|
||||
|
||||
{"jsonrpc":"2.0", "id":1, "method":"accountSubscribe", "params":["CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12", {"encoding":"jsonParsed"}]}
|
||||
|
||||
// Result
|
||||
{"jsonrpc": "2.0","result": 0,"id": 1}
|
||||
{"jsonrpc": "2.0","result": 23784,"id": 1}
|
||||
```
|
||||
|
||||
#### Notification Format:
|
||||
|
||||
```bash
|
||||
// Binary encoding
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "accountNotification",
|
||||
@@ -1286,10 +1459,43 @@ Subscribe to an account to receive notifications when the lamports or data for a
|
||||
"slot": 5199307
|
||||
},
|
||||
"value": {
|
||||
"data": "9qRxMDwy1ntDhBBoiy4Na9uDLbRTSzUS989mpwz",
|
||||
"data": "11116bv5nS2h3y12kD1yUKeMZvGcKLSjQgX6BeV7u1FrjeJcKfsHPXHRDEHrBesJhZyqnnq9qJeUuF7WHxiuLuL5twc38w2TXNLxnDbjmuR",
|
||||
"executable": false,
|
||||
"lamports": 33594,
|
||||
"owner": "H9oaJujXETwkmjyweuqKPFtk2no4SumoU9A3hi3dC8U6",
|
||||
"owner": "11111111111111111111111111111111",
|
||||
"rentEpoch": 635
|
||||
}
|
||||
},
|
||||
"subscription": 23784
|
||||
}
|
||||
}
|
||||
|
||||
// Parsed-JSON encoding
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "accountNotification",
|
||||
"params": {
|
||||
"result": {
|
||||
"context": {
|
||||
"slot": 5199307
|
||||
},
|
||||
"value": {
|
||||
"data": {
|
||||
"program": "nonce"
|
||||
"parsed": {
|
||||
"type": "initialized",
|
||||
"info": {
|
||||
"authority": "Bbqg1M4YVVfbhEzwA9SpC9FhsaG83YMTYoR4a8oTDLX",
|
||||
"blockhash": "LUaQTmM7WbMRiATdMMHaRGakPtCkc2GHtH57STKXs6k",
|
||||
"feeCalculator": {
|
||||
"lamportsPerSignature": 5000
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"executable": false,
|
||||
"lamports": 33594,
|
||||
"owner": "11111111111111111111111111111111",
|
||||
"rentEpoch": 635
|
||||
}
|
||||
},
|
||||
@@ -1327,7 +1533,11 @@ Subscribe to a program to receive notifications when the lamports or data for a
|
||||
#### Parameters:
|
||||
|
||||
- `<string>` - program_id Pubkey, 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>`. **jsonParsed encoding is UNSTABLE**
|
||||
- (optional) `filters: <array>` - filter results using various [filter objects](jsonrpc-api.md#filters); account must meet all filter criteria to be included in results
|
||||
|
||||
#### Results:
|
||||
|
||||
@@ -1337,17 +1547,22 @@ Subscribe to a program to receive notifications when the lamports or data for a
|
||||
|
||||
```bash
|
||||
// Request
|
||||
{"jsonrpc":"2.0", "id":1, "method":"programSubscribe", "params":["7BwE8yitxiWkD8jVPFvPmV7rs2Znzi4NHzJGLu2dzpUq"]}
|
||||
{"jsonrpc":"2.0", "id":1, "method":"programSubscribe", "params":["11111111111111111111111111111111"]}
|
||||
|
||||
{"jsonrpc":"2.0", "id":1, "method":"programSubscribe", "params":["7BwE8yitxiWkD8jVPFvPmV7rs2Znzi4NHzJGLu2dzpUq", {"commitment": "single"}]}
|
||||
{"jsonrpc":"2.0", "id":1, "method":"programSubscribe", "params":["11111111111111111111111111111111", {"commitment": "single"}]}
|
||||
|
||||
{"jsonrpc":"2.0", "id":1, "method":"programSubscribe", "params":["11111111111111111111111111111111", {"encoding":"jsonParsed"}]}
|
||||
|
||||
{"jsonrpc":"2.0", "id":1, "method":"programSubscribe", "params":["11111111111111111111111111111111", {"filters":[{"dataSize":80}]}]}
|
||||
|
||||
// Result
|
||||
{"jsonrpc": "2.0","result": 0,"id": 1}
|
||||
{"jsonrpc": "2.0","result": 24040,"id": 1}
|
||||
```
|
||||
|
||||
#### Notification Format:
|
||||
|
||||
```bash
|
||||
// Binary encoding
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "programNotification",
|
||||
@@ -1359,10 +1574,46 @@ Subscribe to a program to receive notifications when the lamports or data for a
|
||||
"value": {
|
||||
"pubkey": "H4vnBqifaSACnKa7acsxstsY1iV1bvJNxsCY7enrd1hq"
|
||||
"account": {
|
||||
"data": "9qRxMDwy1ntDhBBoiy4Na9uDLbRTSzUS989m",
|
||||
"data": "11116bv5nS2h3y12kD1yUKeMZvGcKLSjQgX6BeV7u1FrjeJcKfsHPXHRDEHrBesJhZyqnnq9qJeUuF7WHxiuLuL5twc38w2TXNLxnDbjmuR",
|
||||
"executable": false,
|
||||
"lamports": 33594,
|
||||
"owner": "7BwE8yitxiWkD8jVPFvPmV7rs2Znzi4NHzJGLu2dzpUq",
|
||||
"owner": "11111111111111111111111111111111",
|
||||
"rentEpoch": 636
|
||||
},
|
||||
}
|
||||
},
|
||||
"subscription": 24040
|
||||
}
|
||||
}
|
||||
|
||||
// Parsed-JSON encoding
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "programNotification",
|
||||
"params": {
|
||||
"result": {
|
||||
"context": {
|
||||
"slot": 5208469
|
||||
},
|
||||
"value": {
|
||||
"pubkey": "H4vnBqifaSACnKa7acsxstsY1iV1bvJNxsCY7enrd1hq"
|
||||
"account": {
|
||||
"data": {
|
||||
"program": "nonce"
|
||||
"parsed": {
|
||||
"type": "initialized",
|
||||
"info": {
|
||||
"authority": "Bbqg1M4YVVfbhEzwA9SpC9FhsaG83YMTYoR4a8oTDLX",
|
||||
"blockhash": "LUaQTmM7WbMRiATdMMHaRGakPtCkc2GHtH57STKXs6k",
|
||||
"feeCalculator": {
|
||||
"lamportsPerSignature": 5000
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"executable": false,
|
||||
"lamports": 33594,
|
||||
"owner": "11111111111111111111111111111111",
|
||||
"rentEpoch": 636
|
||||
},
|
||||
}
|
||||
|
@@ -1,4 +1,6 @@
|
||||
# solana CLI
|
||||
---
|
||||
title: CLI Usage Reference
|
||||
---
|
||||
|
||||
The [solana-cli crate](https://crates.io/crates/solana-cli) provides a command-line interface tool for Solana
|
||||
|
||||
|
@@ -72,7 +72,7 @@ solana --version
|
||||
installer into a temporary directory:
|
||||
|
||||
```bash
|
||||
curl http://release.solana.com/LATEST_SOLANA_RELEASE_VERSION/solana-install-init-x86_64-pc-windows-gnu.exe --output C:\solana-install-tmp\solana-install-init.exe --create-dirs
|
||||
curl http://release.solana.com/LATEST_SOLANA_RELEASE_VERSION/solana-install-init-x86_64-pc-windows-msvc.exe --output C:\solana-install-tmp\solana-install-init.exe --create-dirs
|
||||
```
|
||||
|
||||
- Copy and paste the following command, then press Enter to install the latest
|
||||
@@ -107,7 +107,7 @@ manually download and install the binaries.
|
||||
|
||||
Download the binaries by navigating to
|
||||
[https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest),
|
||||
download **solana-release-x86_64-unknown-linux-gnu.tar.bz2**, then extract the
|
||||
download **solana-release-x86_64-unknown-linux-msvc.tar.bz2**, then extract the
|
||||
archive:
|
||||
|
||||
```bash
|
||||
@@ -133,7 +133,7 @@ export PATH=$PWD/bin:$PATH
|
||||
|
||||
- Download the binaries by navigating to
|
||||
[https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest),
|
||||
download **solana-release-x86_64-pc-windows-gnu.tar.bz2**, then extract the
|
||||
download **solana-release-x86_64-pc-windows-msvc.tar.bz2**, then extract the
|
||||
archive using WinZip or similar.
|
||||
|
||||
- Open a Command Prompt and navigate to the directory into which you extracted
|
||||
|
106
docs/src/implemented-proposals/rpc-transaction-history.md
Normal file
106
docs/src/implemented-proposals/rpc-transaction-history.md
Normal file
@@ -0,0 +1,106 @@
|
||||
# Long term RPC Transaction History
|
||||
There's a need for RPC to serve at least 6 months of transaction history. The
|
||||
current history, on the order of days, is insufficient for downstream users.
|
||||
|
||||
6 months of transaction data cannot be stored practically in a validator's
|
||||
rocksdb ledger so an external data store is necessary. The validator's
|
||||
rocksdb ledger will continue to serve as the primary data source, and then will
|
||||
fall back to the external data store.
|
||||
|
||||
The affected RPC endpoints are:
|
||||
* [getFirstAvailableBlock](https://docs.solana.com/apps/jsonrpc-api#getfirstavailableblock)
|
||||
* [getConfirmedBlock](https://docs.solana.com/apps/jsonrpc-api#getconfirmedblock)
|
||||
* [getConfirmedBlocks](https://docs.solana.com/apps/jsonrpc-api#getconfirmedblocks)
|
||||
* [getConfirmedSignaturesForAddress](https://docs.solana.com/apps/jsonrpc-api#getconfirmedsignaturesforaddress)
|
||||
* [getConfirmedTransaction](https://docs.solana.com/apps/jsonrpc-api#getconfirmedtransaction)
|
||||
* [getSignatureStatuses](https://docs.solana.com/apps/jsonrpc-api#getsignaturestatuses)
|
||||
|
||||
Note that [getBlockTime](https://docs.solana.com/apps/jsonrpc-api#getblocktime)
|
||||
is not supported, as once https://github.com/solana-labs/solana/issues/10089 is
|
||||
fixed then `getBlockTime` can be removed.
|
||||
|
||||
Some system design constraints:
|
||||
* The volume of data to store and search can quickly jump into the terabytes,
|
||||
and is immutable.
|
||||
* The system should be as light as possible for SREs. For example an SQL
|
||||
database cluster that requires an SRE to continually monitor and rebalance
|
||||
nodes is undesirable.
|
||||
* Data must be searchable in real time - batched queries that take minutes or
|
||||
hours to run are unacceptable.
|
||||
* Easy to replicate the data worldwide to co-locate it with the RPC endpoints
|
||||
that will utilize it.
|
||||
* Interfacing with the external data store should be easy and not require
|
||||
depending on risky lightly-used community-supported code libraries
|
||||
|
||||
Based on these constraints, Google's BigTable product is selected as the data
|
||||
store.
|
||||
|
||||
## Table Schema
|
||||
A BigTable instance is used to hold all transaction data, broken up into
|
||||
different tables for quick searching.
|
||||
|
||||
New data may be copied into the instance at anytime without affecting the existing
|
||||
data, and all data is immutable. Generally the expectation is that new data
|
||||
will be uploaded once an current epoch completes but there is no limitation on
|
||||
the frequency of data dumps.
|
||||
|
||||
Cleanup of old data is automatic by configuring the data retention policy of the
|
||||
instance tables appropriately, it just disappears. Therefore the order of when data is
|
||||
added becomes important. For example if data from epoch N-1 is added after data
|
||||
from epoch N, the older epoch data will outlive the newer data. However beyond
|
||||
producing _holes_ in query results, this kind of unordered deletion will
|
||||
have no ill effect. Note that this method of cleanup effectively allows for an
|
||||
unlimited amount of transaction data to be stored, restricted only by the
|
||||
monetary costs of doing so.
|
||||
|
||||
The table layout s supports the existing RPC endpoints only. New RPC endpoints
|
||||
in the future may require additions to the schema and potentially iterating over
|
||||
all transactions to build up the necessary metadata.
|
||||
|
||||
## Accessing BigTable
|
||||
BigTable has a gRPC endpoint that can be accessed using the
|
||||
[tonic](https://crates.io/crates/crate)] and the raw protobuf API, as currently no
|
||||
higher-level Rust crate for BigTable exists. Practically this makes parsing the
|
||||
results of BigTable queries more complicated but is not a significant issue.
|
||||
|
||||
## Data Population
|
||||
The ongoing population of instance data will occur on an epoch cadence through the
|
||||
use of a new `solana-ledger-tool` command that will convert rocksdb data for a
|
||||
given slot range into the instance schema.
|
||||
|
||||
The same process will be run once, manually, to backfill the existing ledger
|
||||
data.
|
||||
|
||||
### Block Table: `block`
|
||||
|
||||
This table contains the compressed block data for a given slot.
|
||||
|
||||
The row key is generated by taking the 16 digit lower case hexadecimal
|
||||
representation of the slot, to ensure that the oldest slot with a confirmed
|
||||
block will always be first when the rows are listed. eg, The row key for slot
|
||||
42 would be 000000000000002a.
|
||||
|
||||
The row data is a compressed `StoredConfirmedBlock` struct.
|
||||
|
||||
|
||||
### Account Address Transaction Signature Lookup Table: `tx-by-addr`
|
||||
|
||||
This table contains the transactions that affect a given address.
|
||||
|
||||
The row key is `<base58
|
||||
address>/<slot-id-one's-compliment-hex-slot-0-prefixed-to-16-digits>`. The row
|
||||
data is a compressed `TransactionByAddrInfo` struct.
|
||||
|
||||
Taking the one's compliment of the slot allows for listing of slots ensures that
|
||||
the newest slot with transactions that affect an address will always
|
||||
be listed first.
|
||||
|
||||
Sysvar addresses are not indexed. However frequently used programs such as
|
||||
Vote or System are, and will likely have a row for every confirmed slot.
|
||||
|
||||
### Transaction Signature Lookup Table: `tx`
|
||||
|
||||
This table maps a transaction signature to its confirmed block, and index within that block.
|
||||
|
||||
The row key is the base58-encoded transaction signature.
|
||||
The row data is a compressed `TransactionInfo` struct.
|
@@ -168,12 +168,15 @@ vote account on the network. If you have completed this step, you should see the
|
||||
solana-keygen new -o ~/vote-account-keypair.json
|
||||
```
|
||||
|
||||
Create your vote account on the blockchain:
|
||||
The following command can be used to create your vote account on the blockchain
|
||||
with all the default options:
|
||||
|
||||
```bash
|
||||
solana create-vote-account ~/vote-account-keypair.json ~/validator-keypair.json
|
||||
```
|
||||
|
||||
Read more about [creating and managing a vote account](vote-accounts.md).
|
||||
|
||||
## Trusted validators
|
||||
|
||||
If you know and trust other validator nodes, you can specify this on the command line with the `--trusted-validator <PUBKEY>`
|
||||
|
146
docs/src/running-validator/vote-accounts.md
Normal file
146
docs/src/running-validator/vote-accounts.md
Normal file
@@ -0,0 +1,146 @@
|
||||
---
|
||||
title: Vote Account Management
|
||||
---
|
||||
|
||||
This page describes how to set up an on-chain _vote account_. Creating a vote
|
||||
account is needed if you plan to run a validator node on Solana.
|
||||
|
||||
## Create a Vote Account
|
||||
A vote account can be created with the
|
||||
[create-vote-account](../cli/usage.md#solana-create-vote-account) command.
|
||||
The vote account can be configured when first created or after the validator is
|
||||
running. All aspects of the vote account can be changed except for the
|
||||
[vote account address](#vote-account-address), which is fixed for the lifetime
|
||||
of the account.
|
||||
|
||||
### Configure an Existing Vote Account
|
||||
- To change the [validator identity](#validator-identity), use
|
||||
[vote-update-validator](../cli/usage.md#solana-vote-update-validator).
|
||||
- To change the [vote authority](#vote-authority), use
|
||||
[vote-authorize-voter](../cli/usage.md#solana-vote-authorize-voter).
|
||||
- To change the [withdraw authority](#withdraw-authority), use
|
||||
[vote-authorize-withdrawer](../cli/usage.md#solana-vote-authorize-withdrawer).
|
||||
- To change the [commission](#commission), use
|
||||
[vote-update-commission](../cli/usage.md#solana-vote-update-commission).
|
||||
|
||||
## Vote Account Structure
|
||||
|
||||
### Vote Account Address
|
||||
A vote account is created at an address that is either the public key of a
|
||||
keypair file, or at a derived address based on a keypair file's public key and
|
||||
a seed string.
|
||||
|
||||
The address of a vote account is never needed to sign any transactions,
|
||||
but is just used to look up the account information.
|
||||
|
||||
When someone wants to [delegate tokens in a stake account](../staking.md),
|
||||
the delegation command is pointed at the vote account address of the validator
|
||||
to whom the token-holder wants to delegate.
|
||||
|
||||
### Validator Identity
|
||||
|
||||
The _validator identity_ is a system account that is used to pay for all the
|
||||
vote transaction fees submitted to the vote account.
|
||||
Because the validator is expected to vote on most valid blocks it receives,
|
||||
the validator identity account is frequently
|
||||
(potentially multiple times per second) signing transactions and
|
||||
paying fees. For this reason the validator identity keypair must be
|
||||
stored as a "hot wallet" in a keypair file on the same system the validator
|
||||
process is running.
|
||||
|
||||
Because a hot wallet is generally less secure than an offline or "cold" wallet,
|
||||
the validator operator may choose to store only enough SOL on the identity
|
||||
account to cover voting fees for a limited amount of time, such as a few weeks
|
||||
or months. The validator identity account could be periodically topped off
|
||||
from a more secure wallet.
|
||||
|
||||
This practice can reduce the risk of loss of funds if the validator node's
|
||||
disk or file system becomes compromised or corrupted.
|
||||
|
||||
The validator identity is required to be provided when a vote account is created.
|
||||
The validator identity can also be changed after an account is created by using
|
||||
the [vote-update-validator](../cli/usage.md#solana-vote-update-validator) command.
|
||||
|
||||
### Vote Authority
|
||||
|
||||
The _vote authority_ keypair is used to sign each vote transaction the validator
|
||||
node wants to submit to the cluster. This doesn't necessarily have to be unique
|
||||
from the validator identity, as you will see later in this document. Because
|
||||
the vote authority, like the validator identity, is signing transactions
|
||||
frequently, this also must be a hot keypair on the same file system as the
|
||||
validator process.
|
||||
|
||||
The vote authority can be set to the same address as the validator identity.
|
||||
If the validator identity is also the vote authority, only one
|
||||
signature per vote transaction is needed in order to both sign the vote and pay
|
||||
the transaction fee. Because transaction fees on Solana are assessed
|
||||
per-signature, having one signer instead of two will result in half the transaction
|
||||
fee paid compared to setting the vote authority and validator identity to two
|
||||
different accounts.
|
||||
|
||||
The vote authority can be set when the vote account is created. If it is not
|
||||
provided, the default behavior is to assign it the same as the validator identity.
|
||||
The vote authority can be changed later with the
|
||||
[vote-authorize-voter](../cli/usage.md#solana-vote-authorize-voter) command.
|
||||
|
||||
The vote authority can be changed at most once per epoch. If the authority is
|
||||
changed with [vote-authorize-voter](../cli/usage.md#solana-vote-authorize-voter),
|
||||
this will not take effect until the beginning of the next epoch.
|
||||
To support a smooth transition of the vote signing,
|
||||
`solana-validator` allows the `--authorized-voter` argument to be specified
|
||||
multiple times. This allows the validator process to keep voting successfully
|
||||
when the network reaches an epoch boundary at which the validator's vote
|
||||
authority account changes.
|
||||
|
||||
### Withdraw Authority
|
||||
|
||||
The _withdraw authority_ keypair is used to withdraw funds from a vote account
|
||||
using the [withdraw-from-vote-account](../cli/usage.md#solana-withdraw-from-vote-account)
|
||||
command. Any network rewards a validator earns are deposited into the vote
|
||||
account and are only retrievable by signing with the withdraw authority keypair.
|
||||
|
||||
The withdraw authority is also required to sign any transaction to change
|
||||
a vote account's [commission](#commission), and to change the validator
|
||||
identity on a vote account.
|
||||
|
||||
Because the vote account could accrue a significant balance, consider keeping
|
||||
the withdraw authority keypair in an offline/cold wallet, as it is
|
||||
not needed to sign frequent transactions.
|
||||
|
||||
The withdraw authority can be set at vote account creation with the
|
||||
`--authorized-withdrawer` option. If this is not provided, the validator
|
||||
identity will be set as the withdraw authority by default.
|
||||
|
||||
The withdraw authority can be changed later with the
|
||||
[vote-authorize-withdrawer](../cli/usage.md#solana-vote-authorize-withdrawer)
|
||||
command.
|
||||
|
||||
### Commission
|
||||
|
||||
_Commission_ is the percent of network rewards earned by a validator that are
|
||||
deposited into the validator's vote account. The remainder of the rewards
|
||||
are distributed to all of the stake accounts delegated to that vote account,
|
||||
proportional to the active stake weight of each stake account.
|
||||
|
||||
For example, if a vote account has a commission of 10%, for all rewards earned
|
||||
by that validator in a given epoch, 10% of these rewards will be deposited into
|
||||
the vote account in the first block of the following epoch. The remaining 90%
|
||||
will be deposited into delegated stake accounts as immediately active stake.
|
||||
|
||||
A validator may choose to set a low commission to try to attract more stake
|
||||
delegations as a lower commission results in a larger percentage of rewards
|
||||
passed along to the delegator. As there are costs associated with setting up
|
||||
and operating a validator node, a validator would ideally set a high enough
|
||||
commission to at least cover their expenses.
|
||||
|
||||
Commission can be set upon vote account creation with the `--commission` option.
|
||||
If it is not provided, it will default to 100%, which will result in all
|
||||
rewards deposited in the vote account, and none passed on to any delegated
|
||||
stake accounts.
|
||||
|
||||
Commission can also be changed later with the
|
||||
[vote-update-commission](../cli/usage.md#solana-vote-update-commission) command.
|
||||
|
||||
When setting the commission, only integer values in the set [0-100] are accepted.
|
||||
The integer represents the number of percentage points for the commission, so
|
||||
creating an account with `--commission 10` will set a 10% commission.
|
90
docs/src/theme/SearchBar/index.js
Executable file
90
docs/src/theme/SearchBar/index.js
Executable file
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Copyright (c) 2017-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React, {
|
||||
useState,
|
||||
useEffect,
|
||||
useContext,
|
||||
useRef,
|
||||
useCallback,
|
||||
} from "react";
|
||||
import classnames from "classnames";
|
||||
|
||||
import DocusaurusContext from "@docusaurus/context";
|
||||
|
||||
import "./styles.css";
|
||||
|
||||
const Search = (props) => {
|
||||
const [isEnabled, setIsEnabled] = useState(true);
|
||||
const searchBarRef = useRef(null);
|
||||
const context = useContext(DocusaurusContext);
|
||||
|
||||
useEffect(() => {
|
||||
const { siteConfig = {} } = context;
|
||||
const {
|
||||
themeConfig: { algolia },
|
||||
} = siteConfig;
|
||||
|
||||
// https://github.com/algolia/docsearch/issues/352
|
||||
const isClient = typeof window !== "undefined";
|
||||
if (isClient) {
|
||||
import("docsearch.js").then(({ default: docsearch }) => {
|
||||
docsearch({
|
||||
appId: algolia.appId,
|
||||
apiKey: algolia.apiKey,
|
||||
indexName: algolia.indexName,
|
||||
inputSelector: "#search_input_react",
|
||||
algoliaOptions: algolia.algoliaOptions,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
console.warn("Search has failed to load and now is being disabled");
|
||||
setIsEnabled(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const toggleSearchIconClick = useCallback(
|
||||
(e) => {
|
||||
if (!searchBarRef.current.contains(e.target)) {
|
||||
searchBarRef.current.focus();
|
||||
}
|
||||
|
||||
props.handleSearchBarToggle(!props.isSearchBarExpanded);
|
||||
},
|
||||
[props.isSearchBarExpanded]
|
||||
);
|
||||
|
||||
return isEnabled ? (
|
||||
<div className="navbar__search" key="search-box">
|
||||
<span
|
||||
role="button"
|
||||
className={classnames("search-icon", {
|
||||
"search-icon-hidden": props.isSearchBarExpanded,
|
||||
})}
|
||||
onClick={toggleSearchIconClick}
|
||||
onKeyDown={toggleSearchIconClick}
|
||||
tabIndex={0}
|
||||
/>
|
||||
<input
|
||||
id="search_input_react"
|
||||
type="search"
|
||||
placeholder="Search"
|
||||
aria-label="Search"
|
||||
className={classnames(
|
||||
"navbar__search-input",
|
||||
{ "search-bar-expanded": props.isSearchBarExpanded },
|
||||
{ "search-bar": !props.isSearchBarExpanded }
|
||||
)}
|
||||
onFocus={toggleSearchIconClick}
|
||||
onBlur={toggleSearchIconClick}
|
||||
ref={searchBarRef}
|
||||
/>
|
||||
</div>
|
||||
) : null;
|
||||
};
|
||||
|
||||
export default Search;
|
563
docs/src/theme/SearchBar/styles.css
Executable file
563
docs/src/theme/SearchBar/styles.css
Executable file
File diff suppressed because one or more lines are too long
@@ -53,10 +53,10 @@ Solana supports supports several types of wallets in the Solana native
|
||||
command-line app as well as wallets from third-parties.
|
||||
|
||||
For the majority of users, we recommend using one of the
|
||||
[app wallets](apps.md), which will provide a more familiar user
|
||||
[app wallets](wallet-guide/apps.md), which will provide a more familiar user
|
||||
experience rather than needing to learn command line tools.
|
||||
|
||||
For advanced users or developers, the [command-line wallets](cli.md)
|
||||
For advanced users or developers, the [command-line wallets](wallet-guide/cli.md)
|
||||
may be more appropriate, as new features on the Solana blockchain will always be
|
||||
supported on the command line first before being integrated into third-party
|
||||
solutions.
|
||||
|
@@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-dos"
|
||||
version = "1.2.12"
|
||||
version = "1.2.21"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@@ -13,14 +13,14 @@ clap = "2.33.1"
|
||||
log = "0.4.8"
|
||||
rand = "0.7.0"
|
||||
rayon = "1.3.0"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.12" }
|
||||
solana-core = { path = "../core", version = "1.2.12" }
|
||||
solana-ledger = { path = "../ledger", version = "1.2.12" }
|
||||
solana-logger = { path = "../logger", version = "1.2.12" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.2.12" }
|
||||
solana-runtime = { path = "../runtime", version = "1.2.12" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.12" }
|
||||
solana-version = { path = "../version", version = "1.2.12" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.21" }
|
||||
solana-core = { path = "../core", version = "1.2.21" }
|
||||
solana-ledger = { path = "../ledger", version = "1.2.21" }
|
||||
solana-logger = { path = "../logger", version = "1.2.21" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.2.21" }
|
||||
solana-runtime = { path = "../runtime", version = "1.2.21" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.21" }
|
||||
solana-version = { path = "../version", version = "1.2.21" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-download-utils"
|
||||
version = "1.2.12"
|
||||
version = "1.2.21"
|
||||
description = "Solana Download Utils"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@@ -14,8 +14,8 @@ console = "0.10.1"
|
||||
indicatif = "0.14.0"
|
||||
log = "0.4.8"
|
||||
reqwest = { version = "0.10.4", default-features = false, features = ["blocking", "rustls-tls", "json"] }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.12" }
|
||||
solana-ledger = { path = "../ledger", version = "1.2.12" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.21" }
|
||||
solana-ledger = { path = "../ledger", version = "1.2.21" }
|
||||
tar = "0.4.28"
|
||||
|
||||
[lib]
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-faucet"
|
||||
version = "1.2.12"
|
||||
version = "1.2.21"
|
||||
description = "Solana Faucet"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@@ -16,11 +16,11 @@ clap = "2.33"
|
||||
log = "0.4.8"
|
||||
serde = "1.0.110"
|
||||
serde_derive = "1.0.103"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.12" }
|
||||
solana-logger = { path = "../logger", version = "1.2.12" }
|
||||
solana-metrics = { path = "../metrics", version = "1.2.12" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.12" }
|
||||
solana-version = { path = "../version", version = "1.2.12" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.21" }
|
||||
solana-logger = { path = "../logger", version = "1.2.21" }
|
||||
solana-metrics = { path = "../metrics", version = "1.2.21" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.21" }
|
||||
solana-version = { path = "../version", version = "1.2.21" }
|
||||
tokio = "0.1"
|
||||
tokio-codec = "0.1"
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
PERF_LIBS_VERSION=v0.19.0
|
||||
PERF_LIBS_VERSION=v0.19.1
|
||||
VERSION=$PERF_LIBS_VERSION-1
|
||||
|
||||
set -e
|
||||
|
50
fetch-spl.sh
Executable file
50
fetch-spl.sh
Executable file
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Fetches the latest SPL programs and produces the solana-genesis command-line
|
||||
# arguments needed to install them
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
fetch_program() {
|
||||
declare name=$1
|
||||
declare version=$2
|
||||
declare address=$3
|
||||
|
||||
declare so=spl_$name-$version.so
|
||||
|
||||
genesis_args+=(--bpf-program "$address" "$so")
|
||||
|
||||
if [[ -r $so ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ -r ~/.cache/solana-spl/$so ]]; then
|
||||
cp ~/.cache/solana-spl/"$so" "$so"
|
||||
else
|
||||
echo "Downloading $name $version"
|
||||
(
|
||||
set -x
|
||||
curl -L --retry 5 --retry-delay 2 --retry-connrefused \
|
||||
-o "$so" \
|
||||
"https://github.com/solana-labs/solana-program-library/releases/download/$name-v$version/spl_$name.so"
|
||||
)
|
||||
|
||||
mkdir -p ~/.cache/solana-spl
|
||||
cp "$so" ~/.cache/solana-spl/"$so"
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
fetch_program token 1.0.0 TokenSVp5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o
|
||||
fetch_program memo 1.0.0 Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo
|
||||
|
||||
echo "${genesis_args[@]}" > spl-genesis-args.sh
|
||||
|
||||
echo
|
||||
echo "Available SPL programs:"
|
||||
ls -l spl_*.so
|
||||
|
||||
echo
|
||||
echo "solana-genesis command-line arguments (spl-genesis-args.sh):"
|
||||
cat spl-genesis-args.sh
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-genesis-programs"
|
||||
version = "1.2.12"
|
||||
version = "1.2.21"
|
||||
description = "Solana genesis programs"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@@ -10,12 +10,12 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
log = { version = "0.4.8" }
|
||||
solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "1.2.12" }
|
||||
solana-budget-program = { path = "../programs/budget", version = "1.2.12" }
|
||||
solana-exchange-program = { path = "../programs/exchange", version = "1.2.12" }
|
||||
solana-runtime = { path = "../runtime", version = "1.2.12" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.12" }
|
||||
solana-vest-program = { path = "../programs/vest", version = "1.2.12" }
|
||||
solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "1.2.21" }
|
||||
solana-budget-program = { path = "../programs/budget", version = "1.2.21" }
|
||||
solana-exchange-program = { path = "../programs/exchange", version = "1.2.21" }
|
||||
solana-runtime = { path = "../runtime", version = "1.2.21" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.21" }
|
||||
solana-vest-program = { path = "../programs/vest", version = "1.2.21" }
|
||||
|
||||
[lib]
|
||||
crate-type = ["lib"]
|
||||
|
@@ -1,6 +1,5 @@
|
||||
use solana_sdk::{
|
||||
clock::Epoch, genesis_config::OperatingMode, inflation::Inflation,
|
||||
move_loader::solana_move_loader_program, pubkey::Pubkey,
|
||||
clock::Epoch, genesis_config::OperatingMode, inflation::Inflation, pubkey::Pubkey,
|
||||
};
|
||||
|
||||
#[macro_use]
|
||||
@@ -31,7 +30,7 @@ pub fn get_inflation(operating_mode: OperatingMode, epoch: Epoch) -> Option<Infl
|
||||
68 => Some(Inflation::new_disabled()),
|
||||
// Enable again after the inflation fix has landed:
|
||||
// https://github.com/solana-labs/solana/commit/7cc2a6801bed29a816ef509cfc26a6f2522e46ff
|
||||
72 => Some(Inflation::default()),
|
||||
74 => Some(Inflation::default()),
|
||||
_ => None,
|
||||
},
|
||||
OperatingMode::Stable => match epoch {
|
||||
@@ -57,7 +56,6 @@ pub fn get_programs(operating_mode: OperatingMode, epoch: Epoch) -> Option<Vec<(
|
||||
// Programs that are only available in Development mode
|
||||
solana_budget_program!(),
|
||||
solana_exchange_program!(),
|
||||
solana_move_loader_program(),
|
||||
])
|
||||
} else {
|
||||
None
|
||||
@@ -107,6 +105,11 @@ pub fn get_entered_epoch_callback(operating_mode: OperatingMode) -> EnteredEpoch
|
||||
bank.add_native_program(name, program_id);
|
||||
}
|
||||
}
|
||||
if OperatingMode::Stable == operating_mode {
|
||||
bank.set_cross_program_support(bank.epoch() >= 63);
|
||||
} else {
|
||||
bank.set_cross_program_support(true);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -135,7 +138,7 @@ mod tests {
|
||||
fn test_development_programs() {
|
||||
assert_eq!(
|
||||
get_programs(OperatingMode::Development, 0).unwrap().len(),
|
||||
5
|
||||
4
|
||||
);
|
||||
assert_eq!(get_programs(OperatingMode::Development, 1), None);
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-genesis"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.2.12"
|
||||
version = "1.2.21"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@@ -15,14 +15,14 @@ chrono = "0.4"
|
||||
serde = "1.0.110"
|
||||
serde_json = "1.0.53"
|
||||
serde_yaml = "0.8.12"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.12" }
|
||||
solana-genesis-programs = { path = "../genesis-programs", version = "1.2.12" }
|
||||
solana-ledger = { path = "../ledger", version = "1.2.12" }
|
||||
solana-logger = { path = "../logger", version = "1.2.12" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.12" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.2.12" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.2.12" }
|
||||
solana-version = { path = "../version", version = "1.2.12" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.21" }
|
||||
solana-genesis-programs = { path = "../genesis-programs", version = "1.2.21" }
|
||||
solana-ledger = { path = "../ledger", version = "1.2.21" }
|
||||
solana-logger = { path = "../logger", version = "1.2.21" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.21" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.2.21" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.2.21" }
|
||||
solana-version = { path = "../version", version = "1.2.21" }
|
||||
tempfile = "3.1.0"
|
||||
|
||||
[[bin]]
|
||||
|
@@ -12,7 +12,7 @@ use solana_ledger::{
|
||||
};
|
||||
use solana_sdk::{
|
||||
account::Account,
|
||||
clock,
|
||||
bpf_loader, clock,
|
||||
epoch_schedule::EpochSchedule,
|
||||
fee_calculator::FeeRateGovernor,
|
||||
genesis_config::{GenesisConfig, OperatingMode},
|
||||
@@ -26,7 +26,14 @@ use solana_sdk::{
|
||||
use solana_stake_program::stake_state::{self, StakeState};
|
||||
use solana_vote_program::vote_state::{self, VoteState};
|
||||
use std::{
|
||||
collections::HashMap, error, fs::File, io, path::PathBuf, process, str::FromStr, time::Duration,
|
||||
collections::HashMap,
|
||||
error,
|
||||
fs::File,
|
||||
io::{self, Read},
|
||||
path::PathBuf,
|
||||
process,
|
||||
str::FromStr,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
pub enum AccountFileFormat {
|
||||
@@ -341,6 +348,15 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||
"maximum total uncompressed file size of created genesis archive",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("bpf_program")
|
||||
.long("bpf-program")
|
||||
.value_name("ADDRESS BPF_PROGRAM.SO")
|
||||
.takes_value(true)
|
||||
.number_of_values(2)
|
||||
.multiple(true)
|
||||
.help("Install a BPF program at the given address"),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
let faucet_lamports = value_t!(matches, "faucet_lamports", u64).unwrap_or(0);
|
||||
@@ -535,6 +551,39 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||
|
||||
add_genesis_accounts(&mut genesis_config, issued_lamports - faucet_lamports);
|
||||
|
||||
if let Some(values) = matches.values_of("bpf_program") {
|
||||
let values: Vec<&str> = values.collect::<Vec<_>>();
|
||||
for address_program in values.chunks(2) {
|
||||
match address_program {
|
||||
[address, program] => {
|
||||
let address = address.parse::<Pubkey>().unwrap_or_else(|err| {
|
||||
eprintln!("Error: invalid address {}: {}", address, err);
|
||||
process::exit(1);
|
||||
});
|
||||
|
||||
let mut program_data = vec![];
|
||||
File::open(program)
|
||||
.and_then(|mut file| file.read_to_end(&mut program_data))
|
||||
.unwrap_or_else(|err| {
|
||||
eprintln!("Error: failed to read {}: {}", program, err);
|
||||
process::exit(1);
|
||||
});
|
||||
genesis_config.add_account(
|
||||
address,
|
||||
Account {
|
||||
lamports: genesis_config.rent.minimum_balance(program_data.len()),
|
||||
data: program_data,
|
||||
executable: true,
|
||||
owner: bpf_loader::id(),
|
||||
rent_epoch: 0,
|
||||
},
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
solana_logger::setup();
|
||||
create_new_ledger(
|
||||
&ledger_path,
|
||||
|
@@ -3,20 +3,20 @@ authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-gossip"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.2.12"
|
||||
version = "1.2.21"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33.1"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.12" }
|
||||
solana-core = { path = "../core", version = "1.2.12" }
|
||||
solana-client = { path = "../client", version = "1.2.12" }
|
||||
solana-logger = { path = "../logger", version = "1.2.12" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.2.12" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.12" }
|
||||
solana-version = { path = "../version", version = "1.2.12" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.21" }
|
||||
solana-core = { path = "../core", version = "1.2.21" }
|
||||
solana-client = { path = "../client", version = "1.2.21" }
|
||||
solana-logger = { path = "../logger", version = "1.2.21" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.2.21" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.21" }
|
||||
solana-version = { path = "../version", version = "1.2.21" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-install"
|
||||
description = "The solana cluster software installer"
|
||||
version = "1.2.12"
|
||||
version = "1.2.21"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@@ -24,12 +24,12 @@ reqwest = { version = "0.10.4", default-features = false, features = ["blocking"
|
||||
serde = "1.0.110"
|
||||
serde_derive = "1.0.103"
|
||||
serde_yaml = "0.8.12"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.12" }
|
||||
solana-client = { path = "../client", version = "1.2.12" }
|
||||
solana-config-program = { path = "../programs/config", version = "1.2.12" }
|
||||
solana-logger = { path = "../logger", version = "1.2.12" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.12" }
|
||||
solana-version = { path = "../version", version = "1.2.12" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.21" }
|
||||
solana-client = { path = "../client", version = "1.2.21" }
|
||||
solana-config-program = { path = "../programs/config", version = "1.2.21" }
|
||||
solana-logger = { path = "../logger", version = "1.2.21" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.21" }
|
||||
solana-version = { path = "../version", version = "1.2.21" }
|
||||
semver = "0.9.0"
|
||||
tar = "0.4.28"
|
||||
tempdir = "0.3.7"
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-keygen"
|
||||
version = "1.2.12"
|
||||
version = "1.2.21"
|
||||
description = "Solana key generation utility"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@@ -13,11 +13,11 @@ bs58 = "0.3.1"
|
||||
clap = "2.33"
|
||||
dirs = "2.0.2"
|
||||
num_cpus = "1.13.0"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.12" }
|
||||
solana-cli-config = { path = "../cli-config", version = "1.2.12" }
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "1.2.12" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.12" }
|
||||
solana-version = { path = "../version", version = "1.2.12" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.21" }
|
||||
solana-cli-config = { path = "../cli-config", version = "1.2.21" }
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "1.2.21" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.21" }
|
||||
solana-version = { path = "../version", version = "1.2.21" }
|
||||
tiny-bip39 = "0.7.0"
|
||||
|
||||
[[bin]]
|
||||
|
@@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-ledger-tool"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.2.12"
|
||||
version = "1.2.21"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@@ -12,22 +12,27 @@ homepage = "https://solana.com/"
|
||||
bs58 = "0.3.1"
|
||||
bytecount = "0.6.0"
|
||||
clap = "2.33.1"
|
||||
futures = "0.3.5"
|
||||
futures-util = "0.3.5"
|
||||
histogram = "*"
|
||||
log = { version = "0.4.8" }
|
||||
regex = "1"
|
||||
serde_json = "1.0.53"
|
||||
serde_yaml = "0.8.12"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.12" }
|
||||
solana-cli = { path = "../cli", version = "1.2.12" }
|
||||
solana-ledger = { path = "../ledger", version = "1.2.12" }
|
||||
solana-logger = { path = "../logger", version = "1.2.12" }
|
||||
solana-runtime = { path = "../runtime", version = "1.2.12" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.12" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.2.12" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "1.2.12" }
|
||||
solana-version = { path = "../version", version = "1.2.12" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.2.12" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.21" }
|
||||
solana-cli = { path = "../cli", version = "1.2.21" }
|
||||
solana-ledger = { path = "../ledger", version = "1.2.21" }
|
||||
solana-logger = { path = "../logger", version = "1.2.21" }
|
||||
solana-measure = { path = "../measure", version = "1.2.21" }
|
||||
solana-runtime = { path = "../runtime", version = "1.2.21" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.21" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.2.21" }
|
||||
solana-storage-bigtable = { path = "../storage-bigtable", version = "1.2.21" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "1.2.21" }
|
||||
solana-version = { path = "../version", version = "1.2.21" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.2.21" }
|
||||
tempfile = "3.1.0"
|
||||
regex = "1"
|
||||
tokio = { version = "0.2.22", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "1.0"
|
||||
|
551
ledger-tool/src/bigtable.rs
Normal file
551
ledger-tool/src/bigtable.rs
Normal file
@@ -0,0 +1,551 @@
|
||||
/// The `bigtable` subcommand
|
||||
use clap::{value_t, value_t_or_exit, App, AppSettings, Arg, ArgMatches, SubCommand};
|
||||
use log::*;
|
||||
use solana_clap_utils::{
|
||||
input_parsers::pubkey_of,
|
||||
input_validators::{is_slot, is_valid_pubkey},
|
||||
};
|
||||
use solana_cli::display::println_transaction;
|
||||
use solana_ledger::{blockstore::Blockstore, blockstore_db::AccessType};
|
||||
use solana_measure::measure::Measure;
|
||||
use solana_sdk::{clock::Slot, pubkey::Pubkey, signature::Signature};
|
||||
use solana_transaction_status::UiTransactionEncoding;
|
||||
use std::{collections::HashSet, path::Path, process::exit, result::Result, time::Duration};
|
||||
use tokio::time::delay_for;
|
||||
|
||||
// Attempt to upload this many blocks in parallel
|
||||
const NUM_BLOCKS_TO_UPLOAD_IN_PARALLEL: usize = 32;
|
||||
|
||||
// Read up to this many blocks from blockstore before blocking on the upload process
|
||||
const BLOCK_READ_AHEAD_DEPTH: usize = NUM_BLOCKS_TO_UPLOAD_IN_PARALLEL * 2;
|
||||
|
||||
async fn upload(
|
||||
blockstore: Blockstore,
|
||||
starting_slot: Slot,
|
||||
ending_slot: Option<Slot>,
|
||||
allow_missing_metadata: bool,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut measure = Measure::start("entire upload");
|
||||
|
||||
let bigtable = solana_storage_bigtable::LedgerStorage::new(false)
|
||||
.await
|
||||
.map_err(|err| format!("Failed to connect to storage: {:?}", err))?;
|
||||
|
||||
info!("Loading ledger slots...");
|
||||
let blockstore_slots: Vec<_> = blockstore
|
||||
.slot_meta_iterator(starting_slot)
|
||||
.map_err(|err| {
|
||||
format!(
|
||||
"Failed to load entries starting from slot {}: {:?}",
|
||||
starting_slot, err
|
||||
)
|
||||
})?
|
||||
.filter_map(|(slot, _slot_meta)| {
|
||||
if let Some(ending_slot) = &ending_slot {
|
||||
if slot > *ending_slot {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(slot)
|
||||
})
|
||||
.collect();
|
||||
|
||||
if blockstore_slots.is_empty() {
|
||||
info!("Ledger has no slots in the specified range");
|
||||
return Ok(());
|
||||
}
|
||||
info!(
|
||||
"Found {} slots in the range ({}, {})",
|
||||
blockstore_slots.len(),
|
||||
blockstore_slots.first().unwrap(),
|
||||
blockstore_slots.last().unwrap()
|
||||
);
|
||||
|
||||
let mut blockstore_slots_with_no_confirmed_block = HashSet::new();
|
||||
|
||||
// Gather the blocks that are already present in bigtable, by slot
|
||||
let bigtable_slots = {
|
||||
let mut bigtable_slots = vec![];
|
||||
let first_blockstore_slot = *blockstore_slots.first().unwrap();
|
||||
let last_blockstore_slot = *blockstore_slots.last().unwrap();
|
||||
info!(
|
||||
"Loading list of bigtable blocks between slots {} and {}...",
|
||||
first_blockstore_slot, last_blockstore_slot
|
||||
);
|
||||
|
||||
let mut start_slot = *blockstore_slots.first().unwrap();
|
||||
while start_slot <= last_blockstore_slot {
|
||||
let mut next_bigtable_slots = loop {
|
||||
match bigtable.get_confirmed_blocks(start_slot, 1000).await {
|
||||
Ok(slots) => break slots,
|
||||
Err(err) => {
|
||||
error!("get_confirmed_blocks for {} failed: {:?}", start_slot, err);
|
||||
// Consider exponential backoff...
|
||||
delay_for(Duration::from_secs(2)).await;
|
||||
}
|
||||
}
|
||||
};
|
||||
if next_bigtable_slots.is_empty() {
|
||||
break;
|
||||
}
|
||||
bigtable_slots.append(&mut next_bigtable_slots);
|
||||
start_slot = bigtable_slots.last().unwrap() + 1;
|
||||
}
|
||||
bigtable_slots
|
||||
.into_iter()
|
||||
.filter(|slot| *slot <= last_blockstore_slot)
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
// The blocks that still need to be uploaded is the difference between what's already in the
|
||||
// bigtable and what's in blockstore...
|
||||
let blocks_to_upload = {
|
||||
let blockstore_slots = blockstore_slots.iter().cloned().collect::<HashSet<_>>();
|
||||
let bigtable_slots = bigtable_slots.into_iter().collect::<HashSet<_>>();
|
||||
|
||||
let mut blocks_to_upload = blockstore_slots
|
||||
.difference(&blockstore_slots_with_no_confirmed_block)
|
||||
.cloned()
|
||||
.collect::<HashSet<_>>()
|
||||
.difference(&bigtable_slots)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
blocks_to_upload.sort();
|
||||
blocks_to_upload
|
||||
};
|
||||
|
||||
if blocks_to_upload.is_empty() {
|
||||
info!("No blocks need to be uploaded to bigtable");
|
||||
return Ok(());
|
||||
}
|
||||
info!(
|
||||
"{} blocks to be uploaded to the bucket in the range ({}, {})",
|
||||
blocks_to_upload.len(),
|
||||
blocks_to_upload.first().unwrap(),
|
||||
blocks_to_upload.last().unwrap()
|
||||
);
|
||||
|
||||
// Load the blocks out of blockstore in a separate thread to allow for concurrent block uploading
|
||||
let (_loader_thread, receiver) = {
|
||||
let (sender, receiver) = std::sync::mpsc::sync_channel(BLOCK_READ_AHEAD_DEPTH);
|
||||
(
|
||||
std::thread::spawn(move || {
|
||||
let mut measure = Measure::start("block loader thread");
|
||||
for (i, slot) in blocks_to_upload.iter().enumerate() {
|
||||
let _ = match blockstore.get_confirmed_block(
|
||||
*slot,
|
||||
Some(solana_transaction_status::UiTransactionEncoding::Binary),
|
||||
) {
|
||||
Ok(confirmed_block) => sender.send((*slot, Some(confirmed_block))),
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"Failed to get load confirmed block from slot {}: {:?}",
|
||||
slot, err
|
||||
);
|
||||
sender.send((*slot, None))
|
||||
}
|
||||
};
|
||||
|
||||
if i % NUM_BLOCKS_TO_UPLOAD_IN_PARALLEL == 0 {
|
||||
info!(
|
||||
"{}% of blocks processed ({}/{})",
|
||||
i * 100 / blocks_to_upload.len(),
|
||||
i,
|
||||
blocks_to_upload.len()
|
||||
);
|
||||
}
|
||||
}
|
||||
measure.stop();
|
||||
info!("{} to load {} blocks", measure, blocks_to_upload.len());
|
||||
}),
|
||||
receiver,
|
||||
)
|
||||
};
|
||||
|
||||
let mut failures = 0;
|
||||
use futures::stream::StreamExt;
|
||||
|
||||
let mut stream =
|
||||
tokio::stream::iter(receiver.into_iter()).chunks(NUM_BLOCKS_TO_UPLOAD_IN_PARALLEL);
|
||||
|
||||
while let Some(blocks) = stream.next().await {
|
||||
let mut measure_upload = Measure::start("Upload");
|
||||
let mut num_blocks = blocks.len();
|
||||
info!("Preparing the next {} blocks for upload", num_blocks);
|
||||
|
||||
let uploads = blocks.into_iter().filter_map(|(slot, block)| match block {
|
||||
None => {
|
||||
blockstore_slots_with_no_confirmed_block.insert(slot);
|
||||
num_blocks -= 1;
|
||||
None
|
||||
}
|
||||
Some(confirmed_block) => {
|
||||
if confirmed_block
|
||||
.transactions
|
||||
.iter()
|
||||
.any(|transaction| transaction.meta.is_none())
|
||||
{
|
||||
if allow_missing_metadata {
|
||||
info!("Transaction metadata missing from slot {}", slot);
|
||||
} else {
|
||||
panic!("Transaction metadata missing from slot {}", slot);
|
||||
}
|
||||
}
|
||||
Some(bigtable.upload_confirmed_block(slot, confirmed_block))
|
||||
}
|
||||
});
|
||||
|
||||
for result in futures::future::join_all(uploads).await {
|
||||
if result.is_err() {
|
||||
error!("upload_confirmed_block() failed: {:?}", result.err());
|
||||
failures += 1;
|
||||
}
|
||||
}
|
||||
|
||||
measure_upload.stop();
|
||||
info!("{} for {} blocks", measure_upload, num_blocks);
|
||||
}
|
||||
|
||||
measure.stop();
|
||||
info!("{}", measure);
|
||||
if failures > 0 {
|
||||
Err(format!("Incomplete upload, {} operations failed", failures).into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn first_available_block() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let bigtable = solana_storage_bigtable::LedgerStorage::new(true).await?;
|
||||
match bigtable.get_first_available_block().await? {
|
||||
Some(block) => println!("{}", block),
|
||||
None => println!("No blocks available"),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn block(slot: Slot) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let bigtable = solana_storage_bigtable::LedgerStorage::new(false)
|
||||
.await
|
||||
.map_err(|err| format!("Failed to connect to storage: {:?}", err))?;
|
||||
|
||||
let block = bigtable
|
||||
.get_confirmed_block(slot, UiTransactionEncoding::Binary)
|
||||
.await?;
|
||||
|
||||
println!("Slot: {}", slot);
|
||||
println!("Parent Slot: {}", block.parent_slot);
|
||||
println!("Blockhash: {}", block.blockhash);
|
||||
println!("Previous Blockhash: {}", block.previous_blockhash);
|
||||
if block.block_time.is_some() {
|
||||
println!("Block Time: {:?}", block.block_time);
|
||||
}
|
||||
if !block.rewards.is_empty() {
|
||||
println!("Rewards: {:?}", block.rewards);
|
||||
}
|
||||
for (index, transaction_with_meta) in block.transactions.iter().enumerate() {
|
||||
println!("Transaction {}:", index);
|
||||
println_transaction(
|
||||
&transaction_with_meta.transaction.decode().unwrap(),
|
||||
&transaction_with_meta.meta,
|
||||
" ",
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn blocks(starting_slot: Slot, limit: usize) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let bigtable = solana_storage_bigtable::LedgerStorage::new(false)
|
||||
.await
|
||||
.map_err(|err| format!("Failed to connect to storage: {:?}", err))?;
|
||||
|
||||
let slots = bigtable.get_confirmed_blocks(starting_slot, limit).await?;
|
||||
println!("{:?}", slots);
|
||||
println!("{} blocks found", slots.len());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn confirm(signature: &Signature, verbose: bool) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let bigtable = solana_storage_bigtable::LedgerStorage::new(false)
|
||||
.await
|
||||
.map_err(|err| format!("Failed to connect to storage: {:?}", err))?;
|
||||
|
||||
let transaction_status = bigtable.get_signature_status(signature).await?;
|
||||
|
||||
if verbose {
|
||||
match bigtable
|
||||
.get_confirmed_transaction(signature, UiTransactionEncoding::Binary)
|
||||
.await
|
||||
{
|
||||
Ok(Some(confirmed_transaction)) => {
|
||||
println!(
|
||||
"\nTransaction executed in slot {}:",
|
||||
confirmed_transaction.slot
|
||||
);
|
||||
println_transaction(
|
||||
&confirmed_transaction
|
||||
.transaction
|
||||
.transaction
|
||||
.decode()
|
||||
.expect("Successful decode"),
|
||||
&confirmed_transaction.transaction.meta,
|
||||
" ",
|
||||
);
|
||||
}
|
||||
Ok(None) => println!("Confirmed transaction details not available"),
|
||||
Err(err) => println!("Unable to get confirmed transaction details: {}", err),
|
||||
}
|
||||
println!();
|
||||
}
|
||||
match transaction_status.status {
|
||||
Ok(_) => println!("Confirmed"),
|
||||
Err(err) => println!("Transaction failed: {}", err),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn transaction_history(
|
||||
address: &Pubkey,
|
||||
mut limit: usize,
|
||||
mut before: Option<Signature>,
|
||||
verbose: bool,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let bigtable = solana_storage_bigtable::LedgerStorage::new(true).await?;
|
||||
|
||||
while limit > 0 {
|
||||
let results = bigtable
|
||||
.get_confirmed_signatures_for_address(address, before.as_ref(), limit.min(1000))
|
||||
.await?;
|
||||
|
||||
if results.is_empty() {
|
||||
break;
|
||||
}
|
||||
before = Some(results.last().unwrap().signature);
|
||||
assert!(limit >= results.len());
|
||||
limit = limit.saturating_sub(results.len());
|
||||
|
||||
for result in results {
|
||||
if verbose {
|
||||
println!(
|
||||
"{}, slot={}, memo=\"{}\", status={}",
|
||||
result.signature,
|
||||
result.slot,
|
||||
result.memo.unwrap_or_else(|| "".to_string()),
|
||||
match result.err {
|
||||
None => "Confirmed".to_string(),
|
||||
Some(err) => format!("Failed: {:?}", err),
|
||||
}
|
||||
);
|
||||
} else {
|
||||
println!("{}", result.signature);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub trait BigTableSubCommand {
|
||||
fn bigtable_subcommand(self) -> Self;
|
||||
}
|
||||
|
||||
impl BigTableSubCommand for App<'_, '_> {
|
||||
fn bigtable_subcommand(self) -> Self {
|
||||
self.subcommand(
|
||||
SubCommand::with_name("bigtable")
|
||||
.about("Ledger data on a BigTable instance")
|
||||
.setting(AppSettings::ArgRequiredElseHelp)
|
||||
.subcommand(
|
||||
SubCommand::with_name("upload")
|
||||
.about("Upload the ledger to BigTable")
|
||||
.arg(
|
||||
Arg::with_name("starting_slot")
|
||||
.long("starting-slot")
|
||||
.validator(is_slot)
|
||||
.value_name("SLOT")
|
||||
.takes_value(true)
|
||||
.index(1)
|
||||
.help(
|
||||
"Start uploading at this slot [default: first available slot]",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("ending_slot")
|
||||
.long("ending-slot")
|
||||
.validator(is_slot)
|
||||
.value_name("SLOT")
|
||||
.takes_value(true)
|
||||
.index(2)
|
||||
.help("Stop uploading at this slot [default: last available slot]"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("allow_missing_metadata")
|
||||
.long("allow-missing-metadata")
|
||||
.takes_value(false)
|
||||
.help("Don't panic if transaction metadata is missing"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("first-available-block")
|
||||
.about("Get the first available block in the storage"),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("blocks")
|
||||
.about("Get a list of slots with confirmed blocks for the given range")
|
||||
.arg(
|
||||
Arg::with_name("starting_slot")
|
||||
.long("starting-slot")
|
||||
.validator(is_slot)
|
||||
.value_name("SLOT")
|
||||
.takes_value(true)
|
||||
.index(1)
|
||||
.required(true)
|
||||
.default_value("0")
|
||||
.help("Start listing at this slot"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("limit")
|
||||
.long("limit")
|
||||
.validator(is_slot)
|
||||
.value_name("LIMIT")
|
||||
.takes_value(true)
|
||||
.index(2)
|
||||
.required(true)
|
||||
.default_value("1000")
|
||||
.help("Maximum number of slots to return"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("block")
|
||||
.about("Get a confirmed block")
|
||||
.arg(
|
||||
Arg::with_name("slot")
|
||||
.long("slot")
|
||||
.validator(is_slot)
|
||||
.value_name("SLOT")
|
||||
.takes_value(true)
|
||||
.index(1)
|
||||
.required(true),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("confirm")
|
||||
.about("Confirm transaction by signature")
|
||||
.arg(
|
||||
Arg::with_name("signature")
|
||||
.long("signature")
|
||||
.value_name("TRANSACTION_SIGNATURE")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.index(1)
|
||||
.help("The transaction signature to confirm"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("verbose")
|
||||
.short("v")
|
||||
.long("verbose")
|
||||
.takes_value(false)
|
||||
.help("Show additional information"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("transaction-history")
|
||||
.about(
|
||||
"Show historical transactions affecting the given address \
|
||||
from newest to oldest",
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("address")
|
||||
.index(1)
|
||||
.value_name("ADDRESS")
|
||||
.required(true)
|
||||
.validator(is_valid_pubkey)
|
||||
.help("Account address"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("limit")
|
||||
.long("limit")
|
||||
.takes_value(true)
|
||||
.value_name("LIMIT")
|
||||
.validator(is_slot)
|
||||
.index(2)
|
||||
.default_value("18446744073709551615")
|
||||
.help("Maximum number of transaction signatures to return"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("before")
|
||||
.long("before")
|
||||
.value_name("TRANSACTION_SIGNATURE")
|
||||
.takes_value(true)
|
||||
.help("Start with the first signature older than this one"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("verbose")
|
||||
.short("v")
|
||||
.long("verbose")
|
||||
.takes_value(false)
|
||||
.help("Show additional information"),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bigtable_process_command(ledger_path: &Path, matches: &ArgMatches<'_>) {
|
||||
let mut runtime = tokio::runtime::Runtime::new().unwrap();
|
||||
|
||||
let future = match matches.subcommand() {
|
||||
("upload", Some(arg_matches)) => {
|
||||
let starting_slot = value_t!(arg_matches, "starting_slot", Slot).unwrap_or(0);
|
||||
let ending_slot = value_t!(arg_matches, "ending_slot", Slot).ok();
|
||||
let allow_missing_metadata = arg_matches.is_present("allow_missing_metadata");
|
||||
let blockstore =
|
||||
crate::open_blockstore(&ledger_path, AccessType::TryPrimaryThenSecondary);
|
||||
|
||||
runtime.block_on(upload(
|
||||
blockstore,
|
||||
starting_slot,
|
||||
ending_slot,
|
||||
allow_missing_metadata,
|
||||
))
|
||||
}
|
||||
("first-available-block", Some(_arg_matches)) => runtime.block_on(first_available_block()),
|
||||
("block", Some(arg_matches)) => {
|
||||
let slot = value_t_or_exit!(arg_matches, "slot", Slot);
|
||||
runtime.block_on(block(slot))
|
||||
}
|
||||
("blocks", Some(arg_matches)) => {
|
||||
let starting_slot = value_t_or_exit!(arg_matches, "starting_slot", Slot);
|
||||
let limit = value_t_or_exit!(arg_matches, "limit", usize);
|
||||
|
||||
runtime.block_on(blocks(starting_slot, limit))
|
||||
}
|
||||
("confirm", Some(arg_matches)) => {
|
||||
let signature = arg_matches
|
||||
.value_of("signature")
|
||||
.unwrap()
|
||||
.parse()
|
||||
.expect("Invalid signature");
|
||||
let verbose = arg_matches.is_present("verbose");
|
||||
|
||||
runtime.block_on(confirm(&signature, verbose))
|
||||
}
|
||||
("transaction-history", Some(arg_matches)) => {
|
||||
let address = pubkey_of(arg_matches, "address").unwrap();
|
||||
let limit = value_t_or_exit!(arg_matches, "limit", usize);
|
||||
let before = arg_matches
|
||||
.value_of("before")
|
||||
.map(|signature| signature.parse().expect("Invalid signature"));
|
||||
let verbose = arg_matches.is_present("verbose");
|
||||
|
||||
runtime.block_on(transaction_history(&address, limit, before, verbose))
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
future.unwrap_or_else(|err| {
|
||||
eprintln!("{:?}", err);
|
||||
exit(1);
|
||||
});
|
||||
}
|
@@ -2,6 +2,7 @@ use clap::{
|
||||
crate_description, crate_name, value_t, value_t_or_exit, values_t_or_exit, App, Arg,
|
||||
ArgMatches, SubCommand,
|
||||
};
|
||||
use log::*;
|
||||
use regex::Regex;
|
||||
use serde_json::json;
|
||||
use solana_clap_utils::input_validators::{is_parsable, is_slot};
|
||||
@@ -40,7 +41,8 @@ use std::{
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use log::*;
|
||||
mod bigtable;
|
||||
use bigtable::*;
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum LedgerOutputMethod {
|
||||
@@ -704,6 +706,7 @@ fn main() {
|
||||
.global(true)
|
||||
.help("Use DIR for ledger location"),
|
||||
)
|
||||
.bigtable_subcommand()
|
||||
.subcommand(
|
||||
SubCommand::with_name("print")
|
||||
.about("Print the ledger")
|
||||
@@ -975,6 +978,7 @@ fn main() {
|
||||
});
|
||||
|
||||
match matches.subcommand() {
|
||||
("bigtable", Some(arg_matches)) => bigtable_process_command(&ledger_path, arg_matches),
|
||||
("print", Some(arg_matches)) => {
|
||||
let starting_slot = value_t_or_exit!(arg_matches, "starting_slot", Slot);
|
||||
let allow_dead_slots = arg_matches.is_present("allow_dead_slots");
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-ledger"
|
||||
version = "1.2.12"
|
||||
version = "1.2.21"
|
||||
description = "Solana ledger"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@@ -32,19 +32,19 @@ reed-solomon-erasure = { version = "4.0.2", features = ["simd-accel"] }
|
||||
regex = "1.3.7"
|
||||
serde = "1.0.110"
|
||||
serde_bytes = "0.11.4"
|
||||
solana-transaction-status = { path = "../transaction-status", version = "1.2.12" }
|
||||
solana-genesis-programs = { path = "../genesis-programs", version = "1.2.12" }
|
||||
solana-logger = { path = "../logger", version = "1.2.12" }
|
||||
solana-measure = { path = "../measure", version = "1.2.12" }
|
||||
solana-merkle-tree = { path = "../merkle-tree", version = "1.2.12" }
|
||||
solana-metrics = { path = "../metrics", version = "1.2.12" }
|
||||
solana-perf = { path = "../perf", version = "1.2.12" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "1.2.21" }
|
||||
solana-genesis-programs = { path = "../genesis-programs", version = "1.2.21" }
|
||||
solana-logger = { path = "../logger", version = "1.2.21" }
|
||||
solana-measure = { path = "../measure", version = "1.2.21" }
|
||||
solana-merkle-tree = { path = "../merkle-tree", version = "1.2.21" }
|
||||
solana-metrics = { path = "../metrics", version = "1.2.21" }
|
||||
solana-perf = { path = "../perf", version = "1.2.21" }
|
||||
ed25519-dalek = "1.0.0-pre.3"
|
||||
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "1.2.12" }
|
||||
solana-runtime = { path = "../runtime", version = "1.2.12" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.12" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.2.12" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.2.12" }
|
||||
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "1.2.21" }
|
||||
solana-runtime = { path = "../runtime", version = "1.2.21" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.21" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.2.21" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.2.21" }
|
||||
symlink = "0.1.0"
|
||||
tar = "0.4.28"
|
||||
thiserror = "1.0"
|
||||
@@ -62,7 +62,7 @@ features = ["lz4"]
|
||||
[dev-dependencies]
|
||||
assert_matches = "1.3.0"
|
||||
matches = "0.1.6"
|
||||
solana-budget-program = { path = "../programs/budget", version = "1.2.12" }
|
||||
solana-budget-program = { path = "../programs/budget", version = "1.2.21" }
|
||||
|
||||
[lib]
|
||||
crate-type = ["lib"]
|
||||
|
@@ -37,14 +37,15 @@ use solana_sdk::{
|
||||
transaction::Transaction,
|
||||
};
|
||||
use solana_transaction_status::{
|
||||
ConfirmedBlock, ConfirmedTransaction, EncodedTransaction, Rewards, TransactionStatusMeta,
|
||||
TransactionWithStatusMeta, UiTransactionEncoding, UiTransactionStatusMeta,
|
||||
ConfirmedBlock, ConfirmedTransaction, ConfirmedTransactionStatusWithSignature,
|
||||
EncodedTransaction, Rewards, TransactionStatusMeta, TransactionWithStatusMeta,
|
||||
UiTransactionEncoding, UiTransactionStatusMeta,
|
||||
};
|
||||
use solana_vote_program::{vote_instruction::VoteInstruction, vote_state::TIMESTAMP_SLOT_INTERVAL};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
cmp,
|
||||
collections::HashMap,
|
||||
collections::{HashMap, HashSet},
|
||||
fs,
|
||||
io::{Error as IOError, ErrorKind},
|
||||
path::{Path, PathBuf},
|
||||
@@ -1651,7 +1652,7 @@ impl Blockstore {
|
||||
iterator
|
||||
.map(|transaction| {
|
||||
let signature = transaction.signatures[0];
|
||||
let encoded_transaction = EncodedTransaction::encode(transaction, encoding.clone());
|
||||
let encoded_transaction = EncodedTransaction::encode(transaction, encoding);
|
||||
TransactionWithStatusMeta {
|
||||
transaction: encoded_transaction,
|
||||
meta: self
|
||||
@@ -1796,9 +1797,9 @@ impl Blockstore {
|
||||
(transaction_status_cf_primary_index, signature, 0),
|
||||
IteratorDirection::Forward,
|
||||
))?;
|
||||
for ((_, sig, slot), data) in index_iterator {
|
||||
for ((i, sig, slot), data) in index_iterator {
|
||||
counter += 1;
|
||||
if sig != signature {
|
||||
if i != transaction_status_cf_primary_index || sig != signature {
|
||||
break;
|
||||
}
|
||||
if self.is_root(slot) {
|
||||
@@ -1834,8 +1835,9 @@ impl Blockstore {
|
||||
("method", "get_confirmed_transaction".to_string(), String)
|
||||
);
|
||||
if let Some((slot, status)) = self.get_transaction_status(signature.clone())? {
|
||||
let transaction = self.find_transaction_in_slot(slot, signature)?
|
||||
.expect("Transaction to exist in slot entries if it exists in statuses and hasn't been cleaned up");
|
||||
let transaction = self
|
||||
.find_transaction_in_slot(slot, signature)?
|
||||
.ok_or(BlockstoreError::TransactionStatusSlotMismatch)?; // Should not happen
|
||||
let encoding = encoding.unwrap_or(UiTransactionEncoding::Json);
|
||||
let encoded_transaction = EncodedTransaction::encode(transaction, encoding);
|
||||
Ok(Some(ConfirmedTransaction {
|
||||
@@ -1864,7 +1866,8 @@ impl Blockstore {
|
||||
}
|
||||
|
||||
// Returns all cached signatures for an address, ordered by slot that the transaction was
|
||||
// processed in
|
||||
// processed in. Within each slot the transactions will be ordered by signature, and NOT by
|
||||
// the order in which the transactions exist in the block
|
||||
fn find_address_signatures(
|
||||
&self,
|
||||
pubkey: Pubkey,
|
||||
@@ -1896,6 +1899,30 @@ impl Blockstore {
|
||||
Ok(signatures)
|
||||
}
|
||||
|
||||
fn get_lowest_slot_for_address(&self, address: Pubkey) -> Result<Option<Slot>> {
|
||||
let mut lowest_slot = None;
|
||||
for transaction_status_cf_primary_index in 0..=1 {
|
||||
let mut index_iterator = self.address_signatures_cf.iter(IteratorMode::From(
|
||||
(
|
||||
transaction_status_cf_primary_index,
|
||||
address,
|
||||
0,
|
||||
Signature::default(),
|
||||
),
|
||||
IteratorDirection::Forward,
|
||||
))?;
|
||||
if let Some(((i, key_address, slot, _), _)) = index_iterator.next() {
|
||||
if i == transaction_status_cf_primary_index
|
||||
&& key_address == address
|
||||
&& slot < lowest_slot.unwrap_or(Slot::MAX)
|
||||
{
|
||||
lowest_slot = Some(slot);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(lowest_slot)
|
||||
}
|
||||
|
||||
pub fn get_confirmed_signatures_for_address(
|
||||
&self,
|
||||
pubkey: Pubkey,
|
||||
@@ -1914,6 +1941,126 @@ impl Blockstore {
|
||||
.map(|signatures| signatures.iter().map(|(_, signature)| *signature).collect())
|
||||
}
|
||||
|
||||
pub fn get_confirmed_signatures_for_address2(
|
||||
&self,
|
||||
address: Pubkey,
|
||||
highest_confirmed_root: Slot,
|
||||
before: Option<Signature>,
|
||||
limit: usize,
|
||||
) -> Result<Vec<ConfirmedTransactionStatusWithSignature>> {
|
||||
datapoint_info!(
|
||||
"blockstore-rpc-api",
|
||||
(
|
||||
"method",
|
||||
"get_confirmed_signatures_for_address2".to_string(),
|
||||
String
|
||||
)
|
||||
);
|
||||
|
||||
// Figure the `slot` to start listing signatures at, based on the ledger location of the
|
||||
// `before` signature if present. Also generate a HashSet of signatures that should
|
||||
// be excluded from the results.
|
||||
let (mut slot, mut excluded_signatures) = match before {
|
||||
None => (highest_confirmed_root, None),
|
||||
Some(before) => {
|
||||
let transaction_status = self.get_transaction_status(before)?;
|
||||
match transaction_status {
|
||||
None => return Ok(vec![]),
|
||||
Some((slot, _)) => {
|
||||
let confirmed_block = self
|
||||
.get_confirmed_block(slot, Some(UiTransactionEncoding::Binary))
|
||||
.map_err(|err| {
|
||||
BlockstoreError::IO(IOError::new(
|
||||
ErrorKind::Other,
|
||||
format!("Unable to get confirmed block: {}", err),
|
||||
))
|
||||
})?;
|
||||
|
||||
// Load all signatures for the block
|
||||
let mut slot_signatures: Vec<_> = confirmed_block
|
||||
.transactions
|
||||
.iter()
|
||||
.filter_map(|transaction_with_meta| {
|
||||
if let Some(transaction) =
|
||||
transaction_with_meta.transaction.decode()
|
||||
{
|
||||
transaction.signatures.into_iter().next()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Sort signatures as a way to entire a stable ordering within a slot, as
|
||||
// `self.find_address_signatures()` is ordered by signatures ordered and
|
||||
// not by block ordering
|
||||
slot_signatures.sort();
|
||||
|
||||
if let Some(pos) = slot_signatures.iter().position(|&x| x == before) {
|
||||
slot_signatures.truncate(pos + 1);
|
||||
}
|
||||
|
||||
(
|
||||
slot,
|
||||
Some(slot_signatures.into_iter().collect::<HashSet<_>>()),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Fetch the list of signatures that affect the given address
|
||||
let first_available_block = self.get_first_available_block()?;
|
||||
let first_address_slot = self.get_lowest_slot_for_address(address)?;
|
||||
if first_address_slot.is_none() {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
let lower_bound = cmp::max(first_available_block, first_address_slot.unwrap());
|
||||
let mut address_signatures = vec![];
|
||||
loop {
|
||||
if address_signatures.len() >= limit {
|
||||
address_signatures.truncate(limit);
|
||||
break;
|
||||
}
|
||||
|
||||
let mut signatures = self.find_address_signatures(address, slot, slot)?;
|
||||
if let Some(excluded_signatures) = excluded_signatures.take() {
|
||||
address_signatures.extend(
|
||||
signatures
|
||||
.into_iter()
|
||||
.filter(|(_, signature)| !excluded_signatures.contains(&signature)),
|
||||
)
|
||||
} else {
|
||||
address_signatures.append(&mut signatures);
|
||||
}
|
||||
excluded_signatures = None;
|
||||
|
||||
if slot == lower_bound {
|
||||
break;
|
||||
}
|
||||
slot -= 1;
|
||||
}
|
||||
address_signatures.truncate(limit);
|
||||
|
||||
// Fill in the status information for each found transaction
|
||||
let mut infos = vec![];
|
||||
for (slot, signature) in address_signatures.into_iter() {
|
||||
let transaction_status = self.get_transaction_status(signature)?;
|
||||
let err = match transaction_status {
|
||||
None => None,
|
||||
Some((_slot, status)) => status.status.err(),
|
||||
};
|
||||
infos.push(ConfirmedTransactionStatusWithSignature {
|
||||
signature,
|
||||
slot,
|
||||
err,
|
||||
memo: None,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(infos)
|
||||
}
|
||||
|
||||
pub fn read_rewards(&self, index: Slot) -> Result<Option<Rewards>> {
|
||||
self.rewards_cf.get(index)
|
||||
}
|
||||
@@ -5942,6 +6089,19 @@ pub mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_transaction_status() {
|
||||
let blockstore_path = get_tmp_ledger_path!();
|
||||
let blockstore = Blockstore::open(&blockstore_path).unwrap();
|
||||
blockstore.set_roots(&[0]).unwrap();
|
||||
assert_eq!(
|
||||
blockstore
|
||||
.get_confirmed_transaction(Signature::default(), None)
|
||||
.unwrap(),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_confirmed_signatures_for_address() {
|
||||
let blockstore_path = get_tmp_ledger_path!();
|
||||
@@ -6082,6 +6242,244 @@ pub mod tests {
|
||||
Blockstore::destroy(&blockstore_path).expect("Expected successful database destruction");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_lowest_slot_for_address() {
|
||||
let blockstore_path = get_tmp_ledger_path!();
|
||||
{
|
||||
let blockstore = Blockstore::open(&blockstore_path).unwrap();
|
||||
let address = Pubkey::new_rand();
|
||||
let address2 = Pubkey::new_rand();
|
||||
let slot = 5;
|
||||
// Add an additional to record to ensure that existent or lower slots in entries for
|
||||
// other addresses do not affect return
|
||||
blockstore
|
||||
.address_signatures_cf
|
||||
.put(
|
||||
(0, address2, slot, Signature::default()),
|
||||
&AddressSignatureMeta { writeable: false },
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
blockstore.get_lowest_slot_for_address(address).unwrap(),
|
||||
None
|
||||
);
|
||||
|
||||
let slot = 200;
|
||||
blockstore
|
||||
.address_signatures_cf
|
||||
.put(
|
||||
(0, address, slot, Signature::default()),
|
||||
&AddressSignatureMeta { writeable: false },
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
blockstore.get_lowest_slot_for_address(address).unwrap(),
|
||||
Some(200)
|
||||
);
|
||||
|
||||
blockstore
|
||||
.address_signatures_cf
|
||||
.put(
|
||||
(1, address, slot, Signature::default()),
|
||||
&AddressSignatureMeta { writeable: false },
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
blockstore.get_lowest_slot_for_address(address).unwrap(),
|
||||
Some(200)
|
||||
);
|
||||
|
||||
let slot = 300;
|
||||
blockstore
|
||||
.address_signatures_cf
|
||||
.put(
|
||||
(1, address, slot, Signature::default()),
|
||||
&AddressSignatureMeta { writeable: false },
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
blockstore.get_lowest_slot_for_address(address).unwrap(),
|
||||
Some(200)
|
||||
);
|
||||
|
||||
let slot = 100;
|
||||
blockstore
|
||||
.address_signatures_cf
|
||||
.put(
|
||||
(1, address, slot, Signature::default()),
|
||||
&AddressSignatureMeta { writeable: false },
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
blockstore.get_lowest_slot_for_address(address).unwrap(),
|
||||
Some(100)
|
||||
);
|
||||
}
|
||||
Blockstore::destroy(&blockstore_path).expect("Expected successful database destruction");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_confirmed_signatures_for_address2() {
|
||||
let blockstore_path = get_tmp_ledger_path!();
|
||||
{
|
||||
let blockstore = Blockstore::open(&blockstore_path).unwrap();
|
||||
|
||||
fn make_slot_entries_with_transaction_addresses(addresses: &[Pubkey]) -> Vec<Entry> {
|
||||
let mut entries: Vec<Entry> = Vec::new();
|
||||
for address in addresses {
|
||||
let transaction = Transaction::new_with_compiled_instructions(
|
||||
&[&Keypair::new()],
|
||||
&[*address],
|
||||
Hash::default(),
|
||||
vec![Pubkey::new_rand()],
|
||||
vec![CompiledInstruction::new(1, &(), vec![0])],
|
||||
);
|
||||
entries.push(next_entry_mut(&mut Hash::default(), 0, vec![transaction]));
|
||||
let mut tick = create_ticks(1, 0, hash(&serialize(address).unwrap()));
|
||||
entries.append(&mut tick);
|
||||
}
|
||||
entries
|
||||
}
|
||||
|
||||
let address0 = Pubkey::new_rand();
|
||||
let address1 = Pubkey::new_rand();
|
||||
|
||||
for slot in 2..=4 {
|
||||
let entries = make_slot_entries_with_transaction_addresses(&[
|
||||
address0, address1, address0, address1,
|
||||
]);
|
||||
let shreds = entries_to_test_shreds(entries.clone(), slot, slot - 1, true, 0);
|
||||
blockstore.insert_shreds(shreds, None, false).unwrap();
|
||||
|
||||
for entry in &entries {
|
||||
for transaction in &entry.transactions {
|
||||
assert_eq!(transaction.signatures.len(), 1);
|
||||
blockstore
|
||||
.write_transaction_status(
|
||||
slot,
|
||||
transaction.signatures[0],
|
||||
transaction.message.account_keys.iter().collect(),
|
||||
vec![],
|
||||
&TransactionStatusMeta::default(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
blockstore.set_roots(&[1, 2, 3, 4]).unwrap();
|
||||
let highest_confirmed_root = 4;
|
||||
|
||||
// Fetch all signatures for address 0 at once...
|
||||
let all0 = blockstore
|
||||
.get_confirmed_signatures_for_address2(
|
||||
address0,
|
||||
highest_confirmed_root,
|
||||
None,
|
||||
usize::MAX,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(all0.len(), 6);
|
||||
|
||||
// Fetch all signatures for address 1 at once...
|
||||
let all1 = blockstore
|
||||
.get_confirmed_signatures_for_address2(
|
||||
address1,
|
||||
highest_confirmed_root,
|
||||
None,
|
||||
usize::MAX,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(all1.len(), 6);
|
||||
|
||||
assert!(all0 != all1);
|
||||
|
||||
// Fetch all signatures for address 0 individually
|
||||
for i in 0..all0.len() {
|
||||
let results = blockstore
|
||||
.get_confirmed_signatures_for_address2(
|
||||
address0,
|
||||
highest_confirmed_root,
|
||||
if i == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(all0[i - 1].signature)
|
||||
},
|
||||
1,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(results.len(), 1);
|
||||
assert_eq!(results[0], all0[i], "Unexpected result for {}", i);
|
||||
}
|
||||
|
||||
assert!(blockstore
|
||||
.get_confirmed_signatures_for_address2(
|
||||
address0,
|
||||
highest_confirmed_root,
|
||||
Some(all0[all0.len() - 1].signature),
|
||||
1,
|
||||
)
|
||||
.unwrap()
|
||||
.is_empty());
|
||||
|
||||
// Fetch all signatures for address 0, three at a time
|
||||
assert!(all0.len() % 3 == 0);
|
||||
for i in (0..all0.len()).step_by(3) {
|
||||
let results = blockstore
|
||||
.get_confirmed_signatures_for_address2(
|
||||
address0,
|
||||
highest_confirmed_root,
|
||||
if i == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(all0[i - 1].signature)
|
||||
},
|
||||
3,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(results.len(), 3);
|
||||
assert_eq!(results[0], all0[i]);
|
||||
assert_eq!(results[1], all0[i + 1]);
|
||||
assert_eq!(results[2], all0[i + 2]);
|
||||
}
|
||||
|
||||
// Ensure that the signatures within a slot are ordered by signature
|
||||
// (current limitation of the .get_confirmed_signatures_for_address2())
|
||||
for i in (0..all1.len()).step_by(2) {
|
||||
let results = blockstore
|
||||
.get_confirmed_signatures_for_address2(
|
||||
address1,
|
||||
highest_confirmed_root,
|
||||
if i == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(all1[i - 1].signature)
|
||||
},
|
||||
2,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(results.len(), 2);
|
||||
assert_eq!(results[0].slot, results[1].slot);
|
||||
assert!(results[0].signature <= results[1].signature);
|
||||
assert_eq!(results[0], all1[i]);
|
||||
assert_eq!(results[1], all1[i + 1]);
|
||||
}
|
||||
|
||||
// A search for address 0 with a `before` signature from address1 should also work
|
||||
let results = blockstore
|
||||
.get_confirmed_signatures_for_address2(
|
||||
address0,
|
||||
highest_confirmed_root,
|
||||
Some(all1[0].signature),
|
||||
usize::MAX,
|
||||
)
|
||||
.unwrap();
|
||||
// The exact number of results returned is variable, based on the sort order of the
|
||||
// random signatures that are generated
|
||||
assert!(!results.is_empty());
|
||||
}
|
||||
Blockstore::destroy(&blockstore_path).expect("Expected successful database destruction");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_last_hash() {
|
||||
let mut entries: Vec<Entry> = vec![];
|
||||
|
@@ -57,6 +57,7 @@ pub enum BlockstoreError {
|
||||
SlotCleanedUp,
|
||||
UnpackError(#[from] UnpackError),
|
||||
UnableToSetOpenFileDescriptorLimit,
|
||||
TransactionStatusSlotMismatch,
|
||||
}
|
||||
pub type Result<T> = std::result::Result<T, BlockstoreError>;
|
||||
|
||||
|
@@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-local-cluster"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.2.12"
|
||||
version = "1.2.21"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@@ -12,22 +12,22 @@ homepage = "https://solana.com/"
|
||||
itertools = "0.9.0"
|
||||
log = "0.4.8"
|
||||
rand = "0.7.0"
|
||||
solana-config-program = { path = "../programs/config", version = "1.2.12" }
|
||||
solana-core = { path = "../core", version = "1.2.12" }
|
||||
solana-client = { path = "../client", version = "1.2.12" }
|
||||
solana-download-utils = { path = "../download-utils", version = "1.2.12" }
|
||||
solana-faucet = { path = "../faucet", version = "1.2.12" }
|
||||
solana-exchange-program = { path = "../programs/exchange", version = "1.2.12" }
|
||||
solana-genesis-programs = { path = "../genesis-programs", version = "1.2.12" }
|
||||
solana-ledger = { path = "../ledger", version = "1.2.12" }
|
||||
solana-logger = { path = "../logger", version = "1.2.12" }
|
||||
solana-runtime = { path = "../runtime", version = "1.2.12" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.12" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.2.12" }
|
||||
solana-vest-program = { path = "../programs/vest", version = "1.2.12" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.2.12" }
|
||||
solana-config-program = { path = "../programs/config", version = "1.2.21" }
|
||||
solana-core = { path = "../core", version = "1.2.21" }
|
||||
solana-client = { path = "../client", version = "1.2.21" }
|
||||
solana-download-utils = { path = "../download-utils", version = "1.2.21" }
|
||||
solana-faucet = { path = "../faucet", version = "1.2.21" }
|
||||
solana-exchange-program = { path = "../programs/exchange", version = "1.2.21" }
|
||||
solana-genesis-programs = { path = "../genesis-programs", version = "1.2.21" }
|
||||
solana-ledger = { path = "../ledger", version = "1.2.21" }
|
||||
solana-logger = { path = "../logger", version = "1.2.21" }
|
||||
solana-runtime = { path = "../runtime", version = "1.2.21" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.21" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.2.21" }
|
||||
solana-vest-program = { path = "../programs/vest", version = "1.2.21" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.2.21" }
|
||||
tempfile = "3.1.0"
|
||||
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "1.2.12" }
|
||||
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "1.2.21" }
|
||||
|
||||
[dev-dependencies]
|
||||
assert_matches = "1.3.0"
|
||||
|
@@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-log-analyzer"
|
||||
description = "The solana cluster network analysis tool"
|
||||
version = "1.2.12"
|
||||
version = "1.2.21"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@@ -14,9 +14,9 @@ byte-unit = "3.1.1"
|
||||
clap = "2.33.1"
|
||||
serde = "1.0.110"
|
||||
serde_json = "1.0.53"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.12" }
|
||||
solana-logger = { path = "../logger", version = "1.2.12" }
|
||||
solana-version = { path = "../version", version = "1.2.12" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.21" }
|
||||
solana-logger = { path = "../logger", version = "1.2.21" }
|
||||
solana-version = { path = "../version", version = "1.2.21" }
|
||||
|
||||
[[bin]]
|
||||
name = "solana-log-analyzer"
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-logger"
|
||||
version = "1.2.12"
|
||||
version = "1.2.21"
|
||||
description = "Solana Logger"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
|
@@ -26,7 +26,7 @@ fn replace_logger(logger: env_logger::Logger) {
|
||||
let max_level = logger.filter();
|
||||
log::set_max_level(max_level);
|
||||
let mut rw = LOGGER.write().unwrap();
|
||||
std::mem::replace(&mut *rw, logger);
|
||||
let _ = std::mem::replace(&mut *rw, logger);
|
||||
let _ = log::set_boxed_logger(Box::new(LoggerShim {}));
|
||||
}
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "solana-measure"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.2.12"
|
||||
version = "1.2.21"
|
||||
documentation = "https://docs.rs/solana"
|
||||
homepage = "https://solana.com/"
|
||||
readme = "../README.md"
|
||||
@@ -12,8 +12,8 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.8"
|
||||
solana-sdk = { path = "../sdk", version = "1.2.12" }
|
||||
solana-metrics = { path = "../metrics", version = "1.2.12" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.21" }
|
||||
solana-metrics = { path = "../metrics", version = "1.2.21" }
|
||||
|
||||
[target."cfg(unix)".dependencies]
|
||||
jemallocator = "0.3.2"
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-merkle-tree"
|
||||
version = "1.2.12"
|
||||
version = "1.2.21"
|
||||
description = "Solana Merkle Tree"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@@ -9,7 +9,7 @@ homepage = "https://solana.com/"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
solana-sdk = { path = "../sdk", version = "1.2.12" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.21" }
|
||||
fast-math = "0.1"
|
||||
|
||||
[dev-dependencies]
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-metrics"
|
||||
version = "1.2.12"
|
||||
version = "1.2.21"
|
||||
description = "Solana Metrics"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@@ -14,7 +14,7 @@ gethostname = "0.2.1"
|
||||
lazy_static = "1.4.0"
|
||||
log = "0.4.8"
|
||||
reqwest = { version = "0.10.4", default-features = false, features = ["blocking", "rustls-tls", "json"] }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.12" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.21" }
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.7.0"
|
||||
|
@@ -334,7 +334,7 @@ lazy_static! {
|
||||
pub fn set_host_id(host_id: String) {
|
||||
let mut rw = HOST_ID.write().unwrap();
|
||||
info!("host id: {}", host_id);
|
||||
std::mem::replace(&mut *rw, host_id);
|
||||
let _ = std::mem::replace(&mut *rw, host_id);
|
||||
}
|
||||
|
||||
/// Submits a new point from any thread. Note that points are internally queued
|
||||
|
@@ -48,6 +48,9 @@ while [[ -n $1 ]]; do
|
||||
elif [[ $1 = --enable-rpc-transaction-history ]]; then
|
||||
args+=("$1")
|
||||
shift
|
||||
elif [[ $1 = --enable-rpc-bigtable-ledger-storage ]]; then
|
||||
args+=("$1")
|
||||
shift
|
||||
elif [[ $1 = --skip-poh-verify ]]; then
|
||||
args+=("$1")
|
||||
shift
|
||||
|
@@ -33,9 +33,19 @@ args=(
|
||||
"$SOLANA_CONFIG_DIR"/bootstrap-validator/vote-account.json
|
||||
"$SOLANA_CONFIG_DIR"/bootstrap-validator/stake-account.json
|
||||
)
|
||||
|
||||
"$SOLANA_ROOT"/fetch-spl.sh
|
||||
if [[ -r spl-genesis-args.sh ]]; then
|
||||
SPL_GENESIS_ARGS=$(cat "$SOLANA_ROOT"/spl-genesis-args.sh)
|
||||
#shellcheck disable=SC2207
|
||||
#shellcheck disable=SC2206
|
||||
args+=($SPL_GENESIS_ARGS)
|
||||
fi
|
||||
|
||||
default_arg --ledger "$SOLANA_CONFIG_DIR"/bootstrap-validator
|
||||
default_arg --faucet-pubkey "$SOLANA_CONFIG_DIR"/faucet.json
|
||||
default_arg --faucet-lamports 500000000000000000
|
||||
default_arg --hashes-per-tick auto
|
||||
default_arg --operating-mode development
|
||||
|
||||
$solana_genesis "${args[@]}"
|
||||
|
@@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-net-shaper"
|
||||
description = "The solana cluster network shaping tool"
|
||||
version = "1.2.12"
|
||||
version = "1.2.21"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@@ -13,8 +13,8 @@ publish = false
|
||||
clap = "2.33.1"
|
||||
serde = "1.0.110"
|
||||
serde_json = "1.0.53"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.12" }
|
||||
solana-logger = { path = "../logger", version = "1.2.12" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.21" }
|
||||
solana-logger = { path = "../logger", version = "1.2.21" }
|
||||
rand = "0.7.0"
|
||||
|
||||
[[bin]]
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-net-utils"
|
||||
version = "1.2.12"
|
||||
version = "1.2.21"
|
||||
description = "Solana Network Utilities"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@@ -18,9 +18,9 @@ rand = "0.7.0"
|
||||
serde = "1.0.110"
|
||||
serde_derive = "1.0.103"
|
||||
socket2 = "0.3.12"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.12" }
|
||||
solana-logger = { path = "../logger", version = "1.2.12" }
|
||||
solana-version = { path = "../version", version = "1.2.12" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.2.21" }
|
||||
solana-logger = { path = "../logger", version = "1.2.21" }
|
||||
solana-version = { path = "../version", version = "1.2.21" }
|
||||
tokio = "0.1"
|
||||
tokio-codec = "0.1"
|
||||
|
||||
|
10
net/net.sh
10
net/net.sh
@@ -93,8 +93,6 @@ Operate a configured testnet
|
||||
--deploy-if-newer - Only deploy if newer software is
|
||||
available (requires -t or -T)
|
||||
|
||||
--use-move - Build the move-loader-program and add it to the cluster
|
||||
|
||||
--operating-mode development|softlaunch
|
||||
- Specify whether or not to launch the cluster in "development" mode with all features enabled at epoch 0,
|
||||
or "softlaunch" mode with some features disabled at epoch 0 (default: development)
|
||||
@@ -187,7 +185,7 @@ build() {
|
||||
|
||||
$MAYBE_DOCKER bash -c "
|
||||
set -ex
|
||||
scripts/cargo-install-all.sh farf \"$buildVariant\" \"$maybeUseMove\"
|
||||
scripts/cargo-install-all.sh farf \"$buildVariant\"
|
||||
"
|
||||
)
|
||||
echo "Build took $SECONDS seconds"
|
||||
@@ -222,7 +220,7 @@ syncScripts() {
|
||||
declare ipAddress=$1
|
||||
rsync -vPrc -e "ssh ${sshOptions[*]}" \
|
||||
--exclude 'net/log*' \
|
||||
"$SOLANA_ROOT"/{fetch-perf-libs.sh,scripts,net,multinode-demo} \
|
||||
"$SOLANA_ROOT"/{fetch-perf-libs.sh,fetch-spl.sh,scripts,net,multinode-demo} \
|
||||
"$ipAddress":~/solana/ > /dev/null
|
||||
}
|
||||
|
||||
@@ -746,7 +744,6 @@ maybeWaitForSupermajority=""
|
||||
debugBuild=false
|
||||
doBuild=true
|
||||
gpuMode=auto
|
||||
maybeUseMove=""
|
||||
netemPartition=""
|
||||
netemConfig=""
|
||||
netemConfigFile=""
|
||||
@@ -826,9 +823,6 @@ while [[ -n $1 ]]; do
|
||||
elif [[ $1 = --debug ]]; then
|
||||
debugBuild=true
|
||||
shift 1
|
||||
elif [[ $1 = --use-move ]]; then
|
||||
maybeUseMove=$1
|
||||
shift 1
|
||||
elif [[ $1 = --partition ]]; then
|
||||
netemPartition=$2
|
||||
shift 2
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-notifier"
|
||||
version = "1.2.12"
|
||||
version = "1.2.21"
|
||||
description = "Solana Notifier"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-perf"
|
||||
version = "1.2.12"
|
||||
version = "1.2.21"
|
||||
description = "Solana Performance APIs"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@@ -17,11 +17,11 @@ serde = "1.0.110"
|
||||
dlopen_derive = "0.1.4"
|
||||
lazy_static = "1.4.0"
|
||||
log = "0.4.8"
|
||||
solana-sdk = { path = "../sdk", version = "1.2.12" }
|
||||
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "1.2.12" }
|
||||
solana-budget-program = { path = "../programs/budget", version = "1.2.12" }
|
||||
solana-logger = { path = "../logger", version = "1.2.12" }
|
||||
solana-metrics = { path = "../metrics", version = "1.2.12" }
|
||||
solana-sdk = { path = "../sdk", version = "1.2.21" }
|
||||
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "1.2.21" }
|
||||
solana-budget-program = { path = "../programs/budget", version = "1.2.21" }
|
||||
solana-logger = { path = "../logger", version = "1.2.21" }
|
||||
solana-metrics = { path = "../metrics", version = "1.2.21" }
|
||||
curve25519-dalek = { version = "2" }
|
||||
|
||||
[lib]
|
||||
|
1918
programs/bpf/Cargo.lock
generated
1918
programs/bpf/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "solana-bpf-programs"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.2.12"
|
||||
version = "1.2.21"
|
||||
documentation = "https://docs.rs/solana"
|
||||
homepage = "https://solana.com/"
|
||||
readme = "README.md"
|
||||
@@ -22,10 +22,10 @@ walkdir = "2"
|
||||
bincode = "1.1.4"
|
||||
byteorder = "1.3.2"
|
||||
elf = "0.0.10"
|
||||
solana-bpf-loader-program = { path = "../bpf_loader", version = "1.2.12" }
|
||||
solana-logger = { path = "../../logger", version = "1.2.12" }
|
||||
solana-runtime = { path = "../../runtime", version = "1.2.12" }
|
||||
solana-sdk = { path = "../../sdk", version = "1.2.12" }
|
||||
solana-bpf-loader-program = { path = "../bpf_loader", version = "1.2.21" }
|
||||
solana-logger = { path = "../../logger", version = "1.2.21" }
|
||||
solana-runtime = { path = "../../runtime", version = "1.2.21" }
|
||||
solana-sdk = { path = "../../sdk", version = "1.2.21" }
|
||||
solana_rbpf = "=0.1.28"
|
||||
|
||||
[[bench]]
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user