Skip to content
This repository was archived by the owner on Jan 22, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions client/src/rpc_response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,3 +192,10 @@ pub struct RpcStorageTurn {
pub blockhash: String,
pub slot: Slot,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct RpcAccountBalance {
pub address: String,
pub lamports: u64,
}
88 changes: 86 additions & 2 deletions core/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use solana_client::{
use solana_faucet::faucet::request_airdrop_transaction;
use solana_ledger::{bank_forks::BankForks, blockstore::Blockstore};
use solana_perf::packet::PACKET_DATA_SIZE;
use solana_runtime::bank::Bank;
use solana_runtime::{accounts::AccountAddressFilter, bank::Bank};
use solana_sdk::{
clock::{Slot, UnixTimestamp},
commitment_config::{CommitmentConfig, CommitmentLevel},
Expand All @@ -37,7 +37,7 @@ use solana_transaction_status::{
use solana_vote_program::vote_state::{VoteState, MAX_LOCKOUT_HISTORY};
use std::{
cmp::max,
collections::HashMap,
collections::{HashMap, HashSet},
net::{SocketAddr, UdpSocket},
str::FromStr,
sync::{Arc, RwLock},
Expand All @@ -46,6 +46,7 @@ use std::{
};

const JSON_RPC_SERVER_ERROR_0: i64 = -32000;
const NUM_LARGEST_ACCOUNTS: usize = 20;

type RpcResponse<T> = Result<Response<T>>;

Expand Down Expand Up @@ -280,6 +281,27 @@ impl JsonRpcRequestProcessor {
Ok(self.bank(commitment)?.capitalization())
}

fn get_largest_accounts(
&self,
commitment: Option<CommitmentConfig>,
) -> RpcResponse<Vec<RpcAccountBalance>> {
let bank = self.bank(commitment)?;
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(),
)
}

fn get_vote_accounts(
&self,
commitment: Option<CommitmentConfig>,
Expand Down Expand Up @@ -730,6 +752,13 @@ pub trait RpcSol {
commitment: Option<CommitmentConfig>,
) -> Result<u64>;

#[rpc(meta, name = "getLargestAccounts")]
fn get_largest_accounts(
Comment thread
CriesofCarrots marked this conversation as resolved.
&self,
meta: Self::Metadata,
commitment: Option<CommitmentConfig>,
) -> RpcResponse<Vec<RpcAccountBalance>>;

#[rpc(meta, name = "requestAirdrop")]
fn request_airdrop(
&self,
Expand Down Expand Up @@ -1131,6 +1160,18 @@ impl RpcSol for RpcSolImpl {
.get_total_supply(commitment)
}

fn get_largest_accounts(
&self,
meta: Self::Metadata,
commitment: Option<CommitmentConfig>,
) -> RpcResponse<Vec<RpcAccountBalance>> {
debug!("get_largest_accounts rpc request received");
meta.request_processor
.read()
.unwrap()
.get_largest_accounts(commitment)
}

fn request_airdrop(
&self,
meta: Self::Metadata,
Expand Down Expand Up @@ -1771,6 +1812,49 @@ pub mod tests {
assert!(supply >= TEST_MINT_LAMPORTS);
}

#[test]
fn test_get_largest_accounts() {
let bob_pubkey = Pubkey::new_rand();
let RpcHandler {
io, meta, alice, ..
} = start_rpc_handler_with_tx(&bob_pubkey);
let req = format!(r#"{{"jsonrpc":"2.0","id":1,"method":"getLargestAccounts"}}"#);
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);

// Get Alice balance
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getBalance","params":["{}"]}}"#,
alice.pubkey()
);
let res = io.handle_request_sync(&req, meta.clone());
let json: Value = serde_json::from_str(&res.unwrap()).unwrap();
let alice_balance: u64 = serde_json::from_value(json["result"]["value"].clone())
.expect("actual response deserialization");
assert!(largest_accounts.contains(&RpcAccountBalance {
address: alice.pubkey().to_string(),
lamports: alice_balance,
}));

// Get Bob balance
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getBalance","params":["{}"]}}"#,
bob_pubkey
);
let res = io.handle_request_sync(&req, meta);
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");
assert!(largest_accounts.contains(&RpcAccountBalance {
address: bob_pubkey.to_string(),
lamports: bob_balance,
}));
}

#[test]
fn test_rpc_get_minimum_balance_for_rent_exemption() {
let bob_pubkey = Pubkey::new_rand();
Expand Down
27 changes: 27 additions & 0 deletions docs/src/apps/jsonrpc-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ To interact with a Solana node inside a JavaScript application, use the [solana-
* [getGenesisHash](jsonrpc-api.md#getgenesishash)
* [getIdentity](jsonrpc-api.md#getidentity)
* [getInflation](jsonrpc-api.md#getinflation)
* [getLargestAccounts](jsonrpc-api.md#getlargestaccounts)
* [getLeaderSchedule](jsonrpc-api.md#getleaderschedule)
* [getMinimumBalanceForRentExemption](jsonrpc-api.md#getminimumbalanceforrentexemption)
* [getProgramAccounts](jsonrpc-api.md#getprogramaccounts)
Expand Down Expand Up @@ -634,6 +635,32 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "m
{"jsonrpc":"2.0","result":{"foundation":0.05,"foundationTerm":7.0,"initial":0.15,"storage":0.1,"taper":0.15,"terminal":0.015},"id":1}
```

### getLargestAccounts

Returns the 20 largest accounts, by lamport balance

#### Parameters:

* `<object>` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)

#### Results:

The result will be an RpcResponse JSON object with `value` equal to an array of:

* `<object>` - otherwise, a JSON object containing:
* `address: <string>`, base-58 encoded address of the account
* `lamports: <u64>`, number of lamports in the account, as a u64

#### Example:

```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getInflation"}' http://localhost:8899

// Result
{"jsonrpc":"2.0","result":{"context":{"slot":54},"value":[{"lamports":999974,"address":"99P8ZgtJYe1buSK8JXkvpLh8xPsCFuLYhz9hQFNw93WJ"},{"lamports":42,"address":"uPwWLo16MVehpyWqsLkK3Ka8nLowWvAHbBChqv2FZeL"},{"lamports":42,"address":"aYJCgU7REfu3XF8b3QhkqgqQvLizx8zxuLBHA25PzDS"},{"lamports":42,"address":"CTvHVtQ4gd4gUcw3bdVgZJJqApXE9nCbbbP4VTS5wE1D"},{"lamports":20,"address":"4fq3xJ6kfrh9RkJQsmVd5gNMvJbuSHfErywvEjNQDPxu"},{"lamports":4,"address":"AXJADheGVp9cruP8WYu46oNkRbeASngN5fPCMVGQqNHa"},{"lamports":2,"address":"8NT8yS6LiwNprgW4yM1jPPow7CwRUotddBVkrkWgYp24"},{"lamports":1,"address":"SysvarEpochSchedu1e111111111111111111111111"},{"lamports":1,"address":"11111111111111111111111111111111"},{"lamports":1,"address":"Stake11111111111111111111111111111111111111"},{"lamports":1,"address":"SysvarC1ock11111111111111111111111111111111"},{"lamports":1,"address":"StakeConfig11111111111111111111111111111111"},{"lamports":1,"address":"SysvarRent111111111111111111111111111111111"},{"lamports":1,"address":"Config1111111111111111111111111111111111111"},{"lamports":1,"address":"SysvarStakeHistory1111111111111111111111111"},{"lamports":1,"address":"SysvarRecentB1ockHashes11111111111111111111"},{"lamports":1,"address":"SysvarFees111111111111111111111111111111111"},{"lamports":1,"address":"Vote111111111111111111111111111111111111111"}]},"id":1}
```

### getLeaderSchedule

Returns the leader schedule for an epoch
Expand Down
38 changes: 38 additions & 0 deletions runtime/src/accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ pub type TransactionLoaders = Vec<Vec<(Pubkey, Account)>>;

pub type TransactionLoadResult = (TransactionAccounts, TransactionLoaders, TransactionRent);

pub enum AccountAddressFilter {
Exclude, // exclude all addresses matching the fiter
Include, // only include addresses matching the filter
}

impl Accounts {
pub fn new(paths: Vec<PathBuf>) -> Self {
Self::new_with_frozen_accounts(paths, &HashMap::default(), &[])
Expand Down Expand Up @@ -407,6 +412,39 @@ impl Accounts {
})
}

pub fn load_largest_accounts(
&self,
ancestors: &Ancestors,
num: usize,
filter_by_address: &HashSet<Pubkey>,
filter: AccountAddressFilter,
) -> Vec<(Pubkey, u64)> {
let mut accounts_balances = self.accounts_db.scan_accounts(
ancestors,
|collector: &mut Vec<(Pubkey, u64)>, option| {
if let Some(data) = option
.filter(|(pubkey, account, _)| {
let should_include_pubkey = match filter {
AccountAddressFilter::Exclude => !filter_by_address.contains(&pubkey),
AccountAddressFilter::Include => filter_by_address.contains(&pubkey),
};
should_include_pubkey
&& account.lamports != 0
&& !(account.lamports == std::u64::MAX
&& account.owner == solana_storage_program::id())
})
.map(|(pubkey, account, _slot)| (*pubkey, account.lamports))
{
collector.push(data)
}
},
);

accounts_balances.sort_by(|a, b| a.1.cmp(&b.1).reverse());
accounts_balances.truncate(num);
accounts_balances
}

#[must_use]
pub fn verify_bank_hash(&self, slot: Slot, ancestors: &Ancestors) -> bool {
if let Err(err) = self.accounts_db.verify_bank_hash(slot, ancestors) {
Expand Down
18 changes: 16 additions & 2 deletions runtime/src/bank.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
//! on behalf of the caller, and a low-level API for when they have
//! already been signed and verified.
use crate::{
accounts::{Accounts, TransactionAccounts, TransactionLoadResult, TransactionLoaders},
accounts::{
AccountAddressFilter, Accounts, TransactionAccounts, TransactionLoadResult,
TransactionLoaders,
},
accounts_db::{AccountsDBSerialize, ErrorCounters, SnapshotStorage, SnapshotStorages},
accounts_index::Ancestors,
blockhash_queue::BlockhashQueue,
Expand Down Expand Up @@ -58,7 +61,7 @@ use solana_stake_program::stake_state::{self, Delegation};
use solana_vote_program::vote_state::VoteState;
use std::{
cell::RefCell,
collections::HashMap,
collections::{HashMap, HashSet},
io::{BufReader, Cursor, Error as IOError, Read},
path::{Path, PathBuf},
rc::Rc,
Expand Down Expand Up @@ -1844,6 +1847,17 @@ impl Bank {
None
}

pub fn get_largest_accounts(
&self,
num: usize,
filter_by_address: &HashSet<Pubkey>,
filter: AccountAddressFilter,
) -> Vec<(Pubkey, u64)> {
self.rc
.accounts
.load_largest_accounts(&self.ancestors, num, filter_by_address, filter)
}

pub fn transaction_count(&self) -> u64 {
self.transaction_count.load(Ordering::Relaxed)
}
Expand Down