Rpc: optionally filter getLargestAccounts by circulating/nonCirculating (#10007)
* Add circ/non-circ filter to getLargestAccounts * Plumb largest accounts into client and cli * Bump timeout toward CI flakiness * Update docs
This commit is contained in:
parent
a9b82cf95b
commit
ee7f15eff1
|
@ -26,6 +26,7 @@ use solana_clap_utils::{
|
|||
use solana_client::{
|
||||
client_error::{ClientErrorKind, Result as ClientResult},
|
||||
rpc_client::RpcClient,
|
||||
rpc_config::RpcLargestAccountsFilter,
|
||||
rpc_response::{RpcAccount, RpcKeyedAccount},
|
||||
};
|
||||
#[cfg(not(test))]
|
||||
|
@ -205,6 +206,10 @@ pub enum CliCommand {
|
|||
GetSlot {
|
||||
commitment_config: CommitmentConfig,
|
||||
},
|
||||
LargestAccounts {
|
||||
commitment_config: CommitmentConfig,
|
||||
filter: Option<RpcLargestAccountsFilter>,
|
||||
},
|
||||
Supply {
|
||||
commitment_config: CommitmentConfig,
|
||||
print_accounts: bool,
|
||||
|
@ -622,6 +627,7 @@ pub fn parse_command(
|
|||
}),
|
||||
("epoch", Some(matches)) => parse_get_epoch(matches),
|
||||
("slot", Some(matches)) => parse_get_slot(matches),
|
||||
("largest-accounts", Some(matches)) => parse_largest_accounts(matches),
|
||||
("supply", Some(matches)) => parse_supply(matches),
|
||||
("total-supply", Some(matches)) => parse_total_supply(matches),
|
||||
("transaction-count", Some(matches)) => parse_get_transaction_count(matches),
|
||||
|
@ -1707,6 +1713,10 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
|
|||
CliCommand::GetSlot { commitment_config } => {
|
||||
process_get_slot(&rpc_client, *commitment_config)
|
||||
}
|
||||
CliCommand::LargestAccounts {
|
||||
commitment_config,
|
||||
filter,
|
||||
} => process_largest_accounts(&rpc_client, config, *commitment_config, filter.clone()),
|
||||
CliCommand::Supply {
|
||||
commitment_config,
|
||||
print_accounts,
|
||||
|
|
|
@ -4,7 +4,9 @@ use console::{style, Emoji};
|
|||
use inflector::cases::titlecase::to_title_case;
|
||||
use serde::Serialize;
|
||||
use serde_json::{Map, Value};
|
||||
use solana_client::rpc_response::{RpcEpochInfo, RpcKeyedAccount, RpcSupply, RpcVoteAccountInfo};
|
||||
use solana_client::rpc_response::{
|
||||
RpcAccountBalance, RpcEpochInfo, RpcKeyedAccount, RpcSupply, RpcVoteAccountInfo,
|
||||
};
|
||||
use solana_sdk::{
|
||||
clock::{self, Epoch, Slot, UnixTimestamp},
|
||||
native_token::lamports_to_sol,
|
||||
|
@ -909,6 +911,30 @@ impl fmt::Display for CliSignature {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct CliAccountBalances {
|
||||
pub accounts: Vec<RpcAccountBalance>,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliAccountBalances {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(
|
||||
f,
|
||||
"{}",
|
||||
style(format!("{:<44} {}", "Address", "Balance",)).bold()
|
||||
)?;
|
||||
for account in &self.accounts {
|
||||
writeln!(
|
||||
f,
|
||||
"{:<44} {}",
|
||||
account.address,
|
||||
&format!("{} SOL", lamports_to_sol(account.lamports))
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct CliSupply {
|
||||
pub total: u64,
|
||||
|
|
|
@ -15,6 +15,7 @@ use solana_clap_utils::{
|
|||
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;
|
||||
|
@ -119,6 +120,23 @@ impl ClusterQuerySubCommands for App<'_, '_> {
|
|||
SubCommand::with_name("epoch").about("Get current epoch")
|
||||
.arg(commitment_arg()),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("largest-accounts").about("Get addresses of largest cluster accounts")
|
||||
.arg(
|
||||
Arg::with_name("circulating")
|
||||
.long("circulating")
|
||||
.takes_value(false)
|
||||
.help("Filter address list to only circulating accounts")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("non_circulating")
|
||||
.long("non-circulating")
|
||||
.takes_value(false)
|
||||
.conflicts_with("circulating")
|
||||
.help("Filter address list to only non-circulating accounts")
|
||||
)
|
||||
.arg(commitment_arg()),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("supply").about("Get information about the cluster supply of SOL")
|
||||
.arg(
|
||||
|
@ -352,6 +370,24 @@ pub fn parse_get_epoch(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliEr
|
|||
})
|
||||
}
|
||||
|
||||
pub fn parse_largest_accounts(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
|
||||
let commitment_config = commitment_of(matches, COMMITMENT_ARG.long).unwrap();
|
||||
let filter = if matches.is_present("circulating") {
|
||||
Some(RpcLargestAccountsFilter::Circulating)
|
||||
} else if matches.is_present("non_circulating") {
|
||||
Some(RpcLargestAccountsFilter::NonCirculating)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(CliCommandInfo {
|
||||
command: CliCommand::LargestAccounts {
|
||||
commitment_config,
|
||||
filter,
|
||||
},
|
||||
signers: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_supply(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
|
||||
let commitment_config = commitment_of(matches, COMMITMENT_ARG.long).unwrap();
|
||||
let print_accounts = matches.is_present("print_accounts");
|
||||
|
@ -815,6 +851,22 @@ pub fn process_show_block_production(
|
|||
Ok(config.output_format.formatted_string(&block_production))
|
||||
}
|
||||
|
||||
pub fn process_largest_accounts(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
commitment_config: CommitmentConfig,
|
||||
filter: Option<RpcLargestAccountsFilter>,
|
||||
) -> ProcessResult {
|
||||
let accounts = rpc_client
|
||||
.get_largest_accounts_with_config(RpcLargestAccountsConfig {
|
||||
commitment: Some(commitment_config),
|
||||
filter,
|
||||
})?
|
||||
.value;
|
||||
let largest_accounts = CliAccountBalances { accounts };
|
||||
Ok(config.output_format.formatted_string(&largest_accounts))
|
||||
}
|
||||
|
||||
pub fn process_supply(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
|
|
|
@ -8,6 +8,7 @@ pub mod perf_utils;
|
|||
pub mod pubsub_client;
|
||||
pub mod rpc_client;
|
||||
pub mod rpc_client_request;
|
||||
pub mod rpc_config;
|
||||
pub mod rpc_request;
|
||||
pub mod rpc_response;
|
||||
pub mod thin_client;
|
||||
|
|
|
@ -3,6 +3,7 @@ use crate::{
|
|||
generic_rpc_client_request::GenericRpcClientRequest,
|
||||
mock_rpc_client_request::{MockRpcClientRequest, Mocks},
|
||||
rpc_client_request::RpcClientRequest,
|
||||
rpc_config::RpcLargestAccountsConfig,
|
||||
rpc_request::{RpcError, RpcRequest},
|
||||
rpc_response::*,
|
||||
};
|
||||
|
@ -197,6 +198,13 @@ impl RpcClient {
|
|||
self.send(RpcRequest::GetTotalSupply, json!([commitment_config]), 0)
|
||||
}
|
||||
|
||||
pub fn get_largest_accounts_with_config(
|
||||
&self,
|
||||
config: RpcLargestAccountsConfig,
|
||||
) -> RpcResult<Vec<RpcAccountBalance>> {
|
||||
self.send(RpcRequest::GetLargestAccounts, json!([config]), 0)
|
||||
}
|
||||
|
||||
pub fn get_vote_accounts(&self) -> ClientResult<RpcVoteAccountStatus> {
|
||||
self.get_vote_accounts_with_commitment(CommitmentConfig::default())
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
use solana_sdk::commitment_config::CommitmentConfig;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcSignatureStatusConfig {
|
||||
pub search_transaction_history: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum RpcLargestAccountsFilter {
|
||||
Circulating,
|
||||
NonCirculating,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcLargestAccountsConfig {
|
||||
#[serde(flatten)]
|
||||
pub commitment: Option<CommitmentConfig>,
|
||||
pub filter: Option<RpcLargestAccountsFilter>,
|
||||
}
|
|
@ -19,6 +19,7 @@ pub enum RpcRequest {
|
|||
GetGenesisHash,
|
||||
GetIdentity,
|
||||
GetInflation,
|
||||
GetLargestAccounts,
|
||||
GetLeaderSchedule,
|
||||
GetProgramAccounts,
|
||||
GetRecentBlockhash,
|
||||
|
@ -62,6 +63,7 @@ impl fmt::Display for RpcRequest {
|
|||
RpcRequest::GetGenesisHash => "getGenesisHash",
|
||||
RpcRequest::GetIdentity => "getIdentity",
|
||||
RpcRequest::GetInflation => "getInflation",
|
||||
RpcRequest::GetLargestAccounts => "getLargestAccounts",
|
||||
RpcRequest::GetLeaderSchedule => "getLeaderSchedule",
|
||||
RpcRequest::GetProgramAccounts => "getProgramAccounts",
|
||||
RpcRequest::GetRecentBlockhash => "getRecentBlockhash",
|
||||
|
@ -91,6 +93,7 @@ impl fmt::Display for RpcRequest {
|
|||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
|
|
107
core/src/rpc.rs
107
core/src/rpc.rs
|
@ -13,8 +13,10 @@ use bincode::serialize;
|
|||
use jsonrpc_core::{Error, Metadata, Result};
|
||||
use jsonrpc_derive::rpc;
|
||||
use solana_client::{
|
||||
rpc_config::*,
|
||||
rpc_request::{
|
||||
MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS_SLOT_RANGE, MAX_GET_SIGNATURE_STATUSES_QUERY_ITEMS,
|
||||
MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS_SLOT_RANGE,
|
||||
MAX_GET_SIGNATURE_STATUSES_QUERY_ITEMS, NUM_LARGEST_ACCOUNTS,
|
||||
},
|
||||
rpc_response::*,
|
||||
};
|
||||
|
@ -49,8 +51,6 @@ use std::{
|
|||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
const NUM_LARGEST_ACCOUNTS: usize = 20;
|
||||
|
||||
type RpcResponse<T> = Result<Response<T>>;
|
||||
|
||||
fn new_response<T>(bank: &Bank, value: T) -> RpcResponse<T> {
|
||||
|
@ -67,12 +67,6 @@ pub struct JsonRpcConfig {
|
|||
pub faucet_addr: Option<SocketAddr>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcSignatureStatusConfig {
|
||||
pub search_transaction_history: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct JsonRpcRequestProcessor {
|
||||
bank_forks: Arc<RwLock<BankForks>>,
|
||||
|
@ -281,22 +275,30 @@ impl JsonRpcRequestProcessor {
|
|||
|
||||
fn get_largest_accounts(
|
||||
&self,
|
||||
commitment: Option<CommitmentConfig>,
|
||||
config: Option<RpcLargestAccountsConfig>,
|
||||
) -> RpcResponse<Vec<RpcAccountBalance>> {
|
||||
let bank = self.bank(commitment)?;
|
||||
let config = config.unwrap_or_default();
|
||||
let bank = self.bank(config.commitment)?;
|
||||
let (addresses, address_filter) = if let Some(filter) = config.filter {
|
||||
let non_circulating_supply = calculate_non_circulating_supply(bank.clone());
|
||||
let addresses = non_circulating_supply.accounts.into_iter().collect();
|
||||
let address_filter = match filter {
|
||||
RpcLargestAccountsFilter::Circulating => AccountAddressFilter::Exclude,
|
||||
RpcLargestAccountsFilter::NonCirculating => AccountAddressFilter::Include,
|
||||
};
|
||||
(addresses, address_filter)
|
||||
} else {
|
||||
(HashSet::new(), AccountAddressFilter::Exclude)
|
||||
};
|
||||
new_response(
|
||||
&bank,
|
||||
bank.get_largest_accounts(
|
||||
NUM_LARGEST_ACCOUNTS,
|
||||
&HashSet::new(),
|
||||
AccountAddressFilter::Exclude,
|
||||
)
|
||||
.into_iter()
|
||||
.map(|(address, lamports)| RpcAccountBalance {
|
||||
address: address.to_string(),
|
||||
lamports,
|
||||
})
|
||||
.collect(),
|
||||
bank.get_largest_accounts(NUM_LARGEST_ACCOUNTS, &addresses, address_filter)
|
||||
.into_iter()
|
||||
.map(|(address, lamports)| RpcAccountBalance {
|
||||
address: address.to_string(),
|
||||
lamports,
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -838,7 +840,7 @@ pub trait RpcSol {
|
|||
fn get_largest_accounts(
|
||||
&self,
|
||||
meta: Self::Metadata,
|
||||
commitment: Option<CommitmentConfig>,
|
||||
config: Option<RpcLargestAccountsConfig>,
|
||||
) -> RpcResponse<Vec<RpcAccountBalance>>;
|
||||
|
||||
#[rpc(meta, name = "getSupply")]
|
||||
|
@ -1255,13 +1257,13 @@ impl RpcSol for RpcSolImpl {
|
|||
fn get_largest_accounts(
|
||||
&self,
|
||||
meta: Self::Metadata,
|
||||
commitment: Option<CommitmentConfig>,
|
||||
config: Option<RpcLargestAccountsConfig>,
|
||||
) -> RpcResponse<Vec<RpcAccountBalance>> {
|
||||
debug!("get_largest_accounts rpc request received");
|
||||
meta.request_processor
|
||||
.read()
|
||||
.unwrap()
|
||||
.get_largest_accounts(commitment)
|
||||
.get_largest_accounts(config)
|
||||
}
|
||||
|
||||
fn get_supply(
|
||||
|
@ -1565,6 +1567,7 @@ pub mod tests {
|
|||
use super::*;
|
||||
use crate::{
|
||||
commitment::BlockCommitment, contact_info::ContactInfo,
|
||||
non_circulating_supply::non_circulating_accounts,
|
||||
replay_stage::tests::create_test_transactions_and_populate_blockstore,
|
||||
};
|
||||
use bincode::deserialize;
|
||||
|
@ -1717,6 +1720,9 @@ pub mod tests {
|
|||
let blockhash = bank.confirmed_last_blockhash().0;
|
||||
let tx = system_transaction::transfer(&alice, pubkey, 20, blockhash);
|
||||
bank.process_transaction(&tx).expect("process transaction");
|
||||
let tx =
|
||||
system_transaction::transfer(&alice, &non_circulating_accounts()[0], 20, blockhash);
|
||||
bank.process_transaction(&tx).expect("process transaction");
|
||||
|
||||
let tx = system_transaction::transfer(&alice, pubkey, std::u64::MAX, blockhash);
|
||||
let _ = bank.process_transaction(&tx);
|
||||
|
@ -1868,7 +1874,7 @@ pub mod tests {
|
|||
|
||||
let req = format!(r#"{{"jsonrpc":"2.0","id":1,"method":"getTransactionCount"}}"#);
|
||||
let res = io.handle_request_sync(&req, meta);
|
||||
let expected = format!(r#"{{"jsonrpc":"2.0","result":3,"id":1}}"#);
|
||||
let expected = format!(r#"{{"jsonrpc":"2.0","result":4,"id":1}}"#);
|
||||
let expected: Response =
|
||||
serde_json::from_str(&expected).expect("expected response deserialization");
|
||||
let result: Response = serde_json::from_str(&res.expect("actual response"))
|
||||
|
@ -1916,6 +1922,31 @@ pub mod tests {
|
|||
assert!(supply >= TEST_MINT_LAMPORTS);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_supply() {
|
||||
let bob_pubkey = Pubkey::new_rand();
|
||||
let RpcHandler { io, meta, .. } = start_rpc_handler_with_tx(&bob_pubkey);
|
||||
let req = format!(r#"{{"jsonrpc":"2.0","id":1,"method":"getSupply"}}"#);
|
||||
let res = io.handle_request_sync(&req, meta.clone());
|
||||
let json: Value = serde_json::from_str(&res.unwrap()).unwrap();
|
||||
let supply: RpcSupply = serde_json::from_value(json["result"]["value"].clone())
|
||||
.expect("actual response deserialization");
|
||||
assert_eq!(supply.non_circulating, 20);
|
||||
assert!(supply.circulating >= TEST_MINT_LAMPORTS);
|
||||
assert!(supply.total >= TEST_MINT_LAMPORTS + 20);
|
||||
let expected_accounts: Vec<String> = non_circulating_accounts()
|
||||
.iter()
|
||||
.map(|pubkey| pubkey.to_string())
|
||||
.collect();
|
||||
assert_eq!(
|
||||
supply.non_circulating_accounts.len(),
|
||||
expected_accounts.len()
|
||||
);
|
||||
for address in supply.non_circulating_accounts {
|
||||
assert!(expected_accounts.contains(&address));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_largest_accounts() {
|
||||
let bob_pubkey = Pubkey::new_rand();
|
||||
|
@ -1928,7 +1959,7 @@ pub mod tests {
|
|||
let largest_accounts: Vec<RpcAccountBalance> =
|
||||
serde_json::from_value(json["result"]["value"].clone())
|
||||
.expect("actual response deserialization");
|
||||
assert_eq!(largest_accounts.len(), 18);
|
||||
assert_eq!(largest_accounts.len(), 19);
|
||||
|
||||
// Get Alice balance
|
||||
let req = format!(
|
||||
|
@ -1949,7 +1980,7 @@ pub mod tests {
|
|||
r#"{{"jsonrpc":"2.0","id":1,"method":"getBalance","params":["{}"]}}"#,
|
||||
bob_pubkey
|
||||
);
|
||||
let res = io.handle_request_sync(&req, meta);
|
||||
let res = io.handle_request_sync(&req, meta.clone());
|
||||
let json: Value = serde_json::from_str(&res.unwrap()).unwrap();
|
||||
let bob_balance: u64 = serde_json::from_value(json["result"]["value"].clone())
|
||||
.expect("actual response deserialization");
|
||||
|
@ -1957,6 +1988,26 @@ pub mod tests {
|
|||
address: bob_pubkey.to_string(),
|
||||
lamports: bob_balance,
|
||||
}));
|
||||
|
||||
// Test Circulating/NonCirculating Filter
|
||||
let req = format!(
|
||||
r#"{{"jsonrpc":"2.0","id":1,"method":"getLargestAccounts","params":[{{"filter":"circulating"}}]}}"#
|
||||
);
|
||||
let res = io.handle_request_sync(&req, meta.clone());
|
||||
let json: Value = serde_json::from_str(&res.unwrap()).unwrap();
|
||||
let largest_accounts: Vec<RpcAccountBalance> =
|
||||
serde_json::from_value(json["result"]["value"].clone())
|
||||
.expect("actual response deserialization");
|
||||
assert_eq!(largest_accounts.len(), 18);
|
||||
let req = format!(
|
||||
r#"{{"jsonrpc":"2.0","id":1,"method":"getLargestAccounts","params":[{{"filter":"nonCirculating"}}]}}"#
|
||||
);
|
||||
let res = io.handle_request_sync(&req, meta.clone());
|
||||
let json: Value = serde_json::from_str(&res.unwrap()).unwrap();
|
||||
let largest_accounts: Vec<RpcAccountBalance> =
|
||||
serde_json::from_value(json["result"]["value"].clone())
|
||||
.expect("actual response deserialization");
|
||||
assert_eq!(largest_accounts.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -265,7 +265,7 @@ fn test_rpc_subscriptions() {
|
|||
}
|
||||
|
||||
// Wait for all signature subscriptions
|
||||
let deadline = Instant::now() + Duration::from_secs(5);
|
||||
let deadline = Instant::now() + Duration::from_secs(7);
|
||||
while !signature_set.is_empty() {
|
||||
let timeout = deadline.saturating_duration_since(Instant::now());
|
||||
match status_receiver.recv_timeout(timeout) {
|
||||
|
|
|
@ -642,7 +642,9 @@ Returns the 20 largest accounts, by lamport balance
|
|||
|
||||
#### Parameters:
|
||||
|
||||
* `<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) `filter: <string>` - filter results by account type; currently supported: `circulating|nonCirculating`
|
||||
|
||||
#### Results:
|
||||
|
||||
|
|
Loading…
Reference in New Issue