Move getSecondaryIndexKeySize RPC to admin_rpc_service (#29003)

* Move getSecondaryIndexKeySize to admin_rpc_service

* Add AdminRpc test framework

* Move test to admin_rpc_service

* Update test

* Remove full-api infra
This commit is contained in:
Tyera 2022-12-19 19:54:05 -07:00 committed by GitHub
parent 6ebbaf30c9
commit c8dd5bbd48
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 492 additions and 391 deletions

2
Cargo.lock generated
View File

@ -6833,6 +6833,7 @@ dependencies = [
"serde_json",
"serde_yaml 0.9.13",
"signal-hook",
"solana-account-decoder",
"solana-clap-utils",
"solana-cli-config",
"solana-core",
@ -6859,6 +6860,7 @@ dependencies = [
"solana-tpu-client",
"solana-version",
"solana-vote-program",
"spl-token-2022",
"symlink",
"tikv-jemallocator",
]

View File

@ -114,14 +114,6 @@ type RpcCustomResult<T> = std::result::Result<T, RpcCustomError>;
pub const MAX_REQUEST_BODY_SIZE: usize = 50 * (1 << 10); // 50kB
pub const PERFORMANCE_SAMPLES_LIMIT: usize = 720;
fn rpc_account_index_from_account_index(account_index: &AccountIndex) -> RpcAccountIndex {
match account_index {
AccountIndex::ProgramId => RpcAccountIndex::ProgramId,
AccountIndex::SplTokenOwner => RpcAccountIndex::SplTokenOwner,
AccountIndex::SplTokenMint => RpcAccountIndex::SplTokenMint,
}
}
fn new_response<T>(bank: &Bank, value: T) -> RpcResponse<T> {
RpcResponse {
context: RpcResponseContext::new(bank.slot()),
@ -504,51 +496,6 @@ impl JsonRpcRequestProcessor {
})
}
pub fn get_secondary_index_key_size(
&self,
index_key: &Pubkey,
config: Option<RpcContextConfig>,
) -> Result<RpcResponse<HashMap<RpcAccountIndex, usize>>> {
// Acquire the bank
let bank = self.get_bank_with_config(config.unwrap_or_default())?;
// Exit if secondary indexes are not enabled
if self.config.account_indexes.is_empty() {
debug!("get_secondary_index_key_size: secondary index not enabled.");
return Ok(new_response(&bank, HashMap::new()));
};
// Make sure the requested key is not explicitly excluded
if !self.config.account_indexes.include_key(index_key) {
return Err(RpcCustomError::KeyExcludedFromSecondaryIndex {
index_key: index_key.to_string(),
}
.into());
}
// Grab a ref to the AccountsIndex for this Bank
let accounts_index = &bank.accounts().accounts_db.accounts_index;
// Find the size of the key in every index where it exists
let found_sizes = self
.config
.account_indexes
.indexes
.iter()
.filter_map(|index| {
accounts_index
.get_index_key_size(index, index_key)
.map(|size| (rpc_account_index_from_account_index(index), size))
})
.collect::<HashMap<_, _>>();
// Note: Will return an empty HashMap if no keys are found.
if found_sizes.is_empty() {
debug!("get_secondary_index_key_size: key not found in the secondary index.");
}
Ok(new_response(&bank, found_sizes))
}
pub async fn get_inflation_reward(
&self,
addresses: Vec<Pubkey>,
@ -2262,7 +2209,7 @@ fn verify_filter(input: &RpcFilterType) -> Result<()> {
.map_err(|e| Error::invalid_params(format!("Invalid param: {e:?}")))
}
fn verify_pubkey(input: &str) -> Result<Pubkey> {
pub fn verify_pubkey(input: &str) -> Result<Pubkey> {
input
.parse()
.map_err(|e| Error::invalid_params(format!("Invalid param: {e:?}")))
@ -3155,16 +3102,6 @@ pub mod rpc_accounts_scan {
config: Option<RpcProgramAccountsConfig>,
) -> Result<OptionalContext<Vec<RpcKeyedAccount>>>;
// This method does not itself do an accounts scan, but returns data only relevant to nodes
// that do support accounts-scan RPC apis
#[rpc(meta, name = "getSecondaryIndexKeySize")]
fn get_secondary_index_key_size(
&self,
meta: Self::Metadata,
pubkey_str: String,
config: Option<RpcContextConfig>,
) -> Result<RpcResponse<HashMap<RpcAccountIndex, usize>>>;
#[rpc(meta, name = "getLargestAccounts")]
fn get_largest_accounts(
&self,
@ -3245,20 +3182,6 @@ pub mod rpc_accounts_scan {
meta.get_program_accounts(&program_id, config, filters, with_context)
}
fn get_secondary_index_key_size(
&self,
meta: Self::Metadata,
pubkey_str: String,
config: Option<RpcContextConfig>,
) -> Result<RpcResponse<HashMap<RpcAccountIndex, usize>>> {
debug!(
"get_secondary_index_key_size rpc request received: {:?}",
pubkey_str
);
let index_key = verify_pubkey(&pubkey_str)?;
meta.get_secondary_index_key_size(&index_key, config)
}
fn get_largest_accounts(
&self,
meta: Self::Metadata,
@ -4562,7 +4485,7 @@ fn sanitize_transaction(
.map_err(|err| Error::invalid_params(format!("invalid transaction: {err}")))
}
pub(crate) fn create_validator_exit(exit: &Arc<AtomicBool>) -> Arc<RwLock<Exit>> {
pub fn create_validator_exit(exit: &Arc<AtomicBool>) -> Arc<RwLock<Exit>> {
let mut validator_exit = Exit::default();
let exit_ = exit.clone();
validator_exit.register_exit(Box::new(move || exit_.store(true, Ordering::Relaxed)));
@ -4672,7 +4595,6 @@ pub mod tests {
jsonrpc_core::{futures, ErrorCode, MetaIoHandler, Output, Response, Value},
jsonrpc_core_client::transports::local,
serde::de::DeserializeOwned,
solana_account_decoder::parse_token::spl_token_pubkey,
solana_address_lookup_table_program::state::{AddressLookupTable, LookupTableMeta},
solana_entry::entry::next_versioned_entry,
solana_gossip::{contact_info::ContactInfo, socketaddr},
@ -7433,316 +7355,6 @@ pub mod tests {
));
}
#[test]
fn test_secondary_index_key_sizes() {
for secondary_index_enabled in [true, false] {
let account_indexes = if secondary_index_enabled {
AccountSecondaryIndexes {
keys: None,
indexes: HashSet::from([
AccountIndex::ProgramId,
AccountIndex::SplTokenMint,
AccountIndex::SplTokenOwner,
]),
}
} else {
AccountSecondaryIndexes::default()
};
// RPC & Bank Setup
let rpc = RpcHandler::start_with_config(JsonRpcConfig {
enable_rpc_transaction_history: true,
account_indexes,
..JsonRpcConfig::default()
});
let bank = rpc.working_bank();
let RpcHandler { io, meta, .. } = rpc;
// Pubkeys
let token_account1_pubkey = Pubkey::new_unique();
let token_account2_pubkey = Pubkey::new_unique();
let token_account3_pubkey = Pubkey::new_unique();
let mint1_pubkey = Pubkey::new_unique();
let mint2_pubkey = Pubkey::new_unique();
let wallet1_pubkey = Pubkey::new_unique();
let wallet2_pubkey = Pubkey::new_unique();
let non_existent_pubkey = Pubkey::new_unique();
let delegate = spl_token_pubkey(&Pubkey::new_unique());
let mut num_default_spl_token_program_accounts = 0;
let mut num_default_system_program_accounts = 0;
if !secondary_index_enabled {
// Test first with no accounts added & no secondary indexes enabled:
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getSecondaryIndexKeySize","params":["{token_account1_pubkey}"]}}"#,
);
let res = io.handle_request_sync(&req, meta.clone());
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
let sizes: HashMap<RpcAccountIndex, usize> =
serde_json::from_value(result["result"]["value"].clone()).unwrap();
assert!(sizes.is_empty());
} else {
// Count SPL Token Program Default Accounts
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getSecondaryIndexKeySize","params":["{}"]}}"#,
inline_spl_token::id(),
);
let res = io.handle_request_sync(&req, meta.clone());
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
let sizes: HashMap<RpcAccountIndex, usize> =
serde_json::from_value(result["result"]["value"].clone()).unwrap();
assert_eq!(sizes.len(), 1);
num_default_spl_token_program_accounts =
*sizes.get(&RpcAccountIndex::ProgramId).unwrap();
// Count System Program Default Accounts
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getSecondaryIndexKeySize","params":["{}"]}}"#,
system_program::id(),
);
let res = io.handle_request_sync(&req, meta.clone());
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
let sizes: HashMap<RpcAccountIndex, usize> =
serde_json::from_value(result["result"]["value"].clone()).unwrap();
assert_eq!(sizes.len(), 1);
num_default_system_program_accounts =
*sizes.get(&RpcAccountIndex::ProgramId).unwrap();
}
// Add 2 basic wallet accounts
let wallet1_account = AccountSharedData::from(Account {
lamports: 11111111,
owner: system_program::id(),
..Account::default()
});
bank.store_account(&wallet1_pubkey, &wallet1_account);
let wallet2_account = AccountSharedData::from(Account {
lamports: 11111111,
owner: system_program::id(),
..Account::default()
});
bank.store_account(&wallet2_pubkey, &wallet2_account);
// Add a token account
let mut account1_data = vec![0; TokenAccount::get_packed_len()];
let token_account1 = TokenAccount {
mint: spl_token_pubkey(&mint1_pubkey),
owner: spl_token_pubkey(&wallet1_pubkey),
delegate: COption::Some(delegate),
amount: 420,
state: TokenAccountState::Initialized,
is_native: COption::None,
delegated_amount: 30,
close_authority: COption::Some(spl_token_pubkey(&wallet1_pubkey)),
};
TokenAccount::pack(token_account1, &mut account1_data).unwrap();
let token_account1 = AccountSharedData::from(Account {
lamports: 111,
data: account1_data.to_vec(),
owner: inline_spl_token::id(),
..Account::default()
});
bank.store_account(&token_account1_pubkey, &token_account1);
// Add the mint
let mut mint1_data = vec![0; Mint::get_packed_len()];
let mint1_state = Mint {
mint_authority: COption::Some(spl_token_pubkey(&wallet1_pubkey)),
supply: 500,
decimals: 2,
is_initialized: true,
freeze_authority: COption::Some(spl_token_pubkey(&wallet1_pubkey)),
};
Mint::pack(mint1_state, &mut mint1_data).unwrap();
let mint_account1 = AccountSharedData::from(Account {
lamports: 222,
data: mint1_data.to_vec(),
owner: inline_spl_token::id(),
..Account::default()
});
bank.store_account(&mint1_pubkey, &mint_account1);
// Add another token account with the different owner, but same delegate, and mint
let mut account2_data = vec![0; TokenAccount::get_packed_len()];
let token_account2 = TokenAccount {
mint: spl_token_pubkey(&mint1_pubkey),
owner: spl_token_pubkey(&wallet2_pubkey),
delegate: COption::Some(delegate),
amount: 420,
state: TokenAccountState::Initialized,
is_native: COption::None,
delegated_amount: 30,
close_authority: COption::Some(spl_token_pubkey(&wallet2_pubkey)),
};
TokenAccount::pack(token_account2, &mut account2_data).unwrap();
let token_account2 = AccountSharedData::from(Account {
lamports: 333,
data: account2_data.to_vec(),
owner: inline_spl_token::id(),
..Account::default()
});
bank.store_account(&token_account2_pubkey, &token_account2);
// Add another token account with the same owner and delegate but different mint
let mut account3_data = vec![0; TokenAccount::get_packed_len()];
let token_account3 = TokenAccount {
mint: spl_token_pubkey(&mint2_pubkey),
owner: spl_token_pubkey(&wallet2_pubkey),
delegate: COption::Some(delegate),
amount: 42,
state: TokenAccountState::Initialized,
is_native: COption::None,
delegated_amount: 30,
close_authority: COption::Some(spl_token_pubkey(&wallet2_pubkey)),
};
TokenAccount::pack(token_account3, &mut account3_data).unwrap();
let token_account3 = AccountSharedData::from(Account {
lamports: 444,
data: account3_data.to_vec(),
owner: inline_spl_token::id(),
..Account::default()
});
bank.store_account(&token_account3_pubkey, &token_account3);
// Add the new mint
let mut mint2_data = vec![0; Mint::get_packed_len()];
let mint2_state = Mint {
mint_authority: COption::Some(spl_token_pubkey(&wallet2_pubkey)),
supply: 200,
decimals: 3,
is_initialized: true,
freeze_authority: COption::Some(spl_token_pubkey(&wallet2_pubkey)),
};
Mint::pack(mint2_state, &mut mint2_data).unwrap();
let mint_account2 = AccountSharedData::from(Account {
lamports: 555,
data: mint2_data.to_vec(),
owner: inline_spl_token::id(),
..Account::default()
});
bank.store_account(&mint2_pubkey, &mint_account2);
// Accounts should now look like the following:
//
// -----system_program------
// / \
// /-(owns) \-(owns)
// / \
// wallet1 ---wallet2---
// / / \
// /-(SPL::owns) /-(SPL::owns) \-(SPL::owns)
// / / \
// token_account1 token_account2 token_account3
// \ / /
// \-(SPL::mint) /-(SPL::mint) /-(SPL::mint)
// \ / /
// --mint_account1-- mint_account2
if secondary_index_enabled {
// ----------- Test for a non-existant key -----------
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getSecondaryIndexKeySize","params":["{non_existent_pubkey}"]}}"#,
);
let res = io.handle_request_sync(&req, meta.clone());
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
let sizes: HashMap<RpcAccountIndex, usize> =
serde_json::from_value(result["result"]["value"].clone()).unwrap();
assert!(sizes.is_empty());
// --------------- Test Queries ---------------
// 1) Wallet1 - Owns 1 SPL Token
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getSecondaryIndexKeySize","params":["{wallet1_pubkey}"]}}"#,
);
let res = io.handle_request_sync(&req, meta.clone());
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
let sizes: HashMap<RpcAccountIndex, usize> =
serde_json::from_value(result["result"]["value"].clone()).unwrap();
assert_eq!(sizes.len(), 1);
assert_eq!(*sizes.get(&RpcAccountIndex::SplTokenOwner).unwrap(), 1);
// 2) Wallet2 - Owns 2 SPL Tokens
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getSecondaryIndexKeySize","params":["{wallet2_pubkey}"]}}"#,
);
let res = io.handle_request_sync(&req, meta.clone());
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
let sizes: HashMap<RpcAccountIndex, usize> =
serde_json::from_value(result["result"]["value"].clone()).unwrap();
assert_eq!(sizes.len(), 1);
assert_eq!(*sizes.get(&RpcAccountIndex::SplTokenOwner).unwrap(), 2);
// 3) Mint1 - Is in 2 SPL Accounts
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getSecondaryIndexKeySize","params":["{mint1_pubkey}"]}}"#,
);
let res = io.handle_request_sync(&req, meta.clone());
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
let sizes: HashMap<RpcAccountIndex, usize> =
serde_json::from_value(result["result"]["value"].clone()).unwrap();
assert_eq!(sizes.len(), 1);
assert_eq!(*sizes.get(&RpcAccountIndex::SplTokenMint).unwrap(), 2);
// 4) Mint2 - Is in 1 SPL Account
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getSecondaryIndexKeySize","params":["{mint2_pubkey}"]}}"#,
);
let res = io.handle_request_sync(&req, meta.clone());
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
let sizes: HashMap<RpcAccountIndex, usize> =
serde_json::from_value(result["result"]["value"].clone()).unwrap();
assert_eq!(sizes.len(), 1);
assert_eq!(*sizes.get(&RpcAccountIndex::SplTokenMint).unwrap(), 1);
// 5) SPL Token Program Owns 6 Accounts - 1 Default, 5 created above.
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getSecondaryIndexKeySize","params":["{}"]}}"#,
inline_spl_token::id(),
);
let res = io.handle_request_sync(&req, meta.clone());
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
let sizes: HashMap<RpcAccountIndex, usize> =
serde_json::from_value(result["result"]["value"].clone()).unwrap();
assert_eq!(sizes.len(), 1);
assert_eq!(
*sizes.get(&RpcAccountIndex::ProgramId).unwrap(),
(num_default_spl_token_program_accounts + 5)
);
// 5) System Program Owns 4 Accounts + 2 Default, 2 created above.
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getSecondaryIndexKeySize","params":["{}"]}}"#,
system_program::id(),
);
let res = io.handle_request_sync(&req, meta.clone());
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
let sizes: HashMap<RpcAccountIndex, usize> =
serde_json::from_value(result["result"]["value"].clone()).unwrap();
assert_eq!(sizes.len(), 1);
assert_eq!(
*sizes.get(&RpcAccountIndex::ProgramId).unwrap(),
(num_default_system_program_accounts + 2)
);
} else {
// ------------ Secondary Indexes Disabled ------------
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getSecondaryIndexKeySize","params":["{token_account2_pubkey}"]}}"#,
);
let res = io.handle_request_sync(&req, meta.clone());
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
let sizes: HashMap<RpcAccountIndex, usize> =
serde_json::from_value(result["result"]["value"].clone()).unwrap();
assert!(sizes.is_empty());
}
}
}
#[test]
fn test_token_rpcs() {
for program_id in solana_account_decoder::parse_token::spl_token_ids() {

View File

@ -59,6 +59,10 @@ solana-version = { path = "../version", version = "=1.15.0" }
solana-vote-program = { path = "../programs/vote", version = "=1.15.0" }
symlink = "0.1.0"
[dev-dependencies]
solana-account-decoder = { path = "../account-decoder", version = "=1.15.0" }
spl-token-2022 = { version = "=0.5.0", features = ["no-entrypoint"] }
[target.'cfg(not(target_env = "msvc"))'.dependencies]
jemallocator = { package = "tikv-jemallocator", version = "0.4.1", features = ["unprefixed_malloc_on_supported_platforms"] }

View File

@ -10,7 +10,9 @@ use {
consensus::Tower, tower_storage::TowerStorage, validator::ValidatorStartProgress,
},
solana_gossip::{cluster_info::ClusterInfo, contact_info::ContactInfo},
solana_runtime::bank_forks::BankForks,
solana_rpc::rpc::verify_pubkey,
solana_rpc_client_api::{config::RpcAccountIndex, custom_error::RpcCustomError},
solana_runtime::{accounts_index::AccountIndex, bank_forks::BankForks},
solana_sdk::{
exit::Exit,
pubkey::Pubkey,
@ -201,6 +203,13 @@ pub trait AdminRpc {
#[rpc(meta, name = "setRepairWhitelist")]
fn set_repair_whitelist(&self, meta: Self::Metadata, whitelist: Vec<Pubkey>) -> Result<()>;
#[rpc(meta, name = "getSecondaryIndexKeySize")]
fn get_secondary_index_key_size(
&self,
meta: Self::Metadata,
pubkey_str: String,
) -> Result<HashMap<RpcAccountIndex, usize>>;
}
pub struct AdminRpcImpl;
@ -368,6 +377,58 @@ impl AdminRpc for AdminRpcImpl {
Ok(())
})
}
fn get_secondary_index_key_size(
&self,
meta: Self::Metadata,
pubkey_str: String,
) -> Result<HashMap<RpcAccountIndex, usize>> {
debug!(
"get_secondary_index_key_size rpc request received: {:?}",
pubkey_str
);
let index_key = verify_pubkey(&pubkey_str)?;
meta.with_post_init(|post_init| {
let bank = post_init.bank_forks.read().unwrap().root_bank();
// Take ref to enabled AccountSecondaryIndexes
let enabled_account_indexes = &bank.accounts().accounts_db.account_indexes;
// Exit if secondary indexes are not enabled
if enabled_account_indexes.is_empty() {
debug!("get_secondary_index_key_size: secondary index not enabled.");
return Ok(HashMap::new());
};
// Make sure the requested key is not explicitly excluded
if !enabled_account_indexes.include_key(&index_key) {
return Err(RpcCustomError::KeyExcludedFromSecondaryIndex {
index_key: index_key.to_string(),
}
.into());
}
// Grab a ref to the AccountsDbfor this Bank
let accounts_index = &bank.accounts().accounts_db.accounts_index;
// Find the size of the key in every index where it exists
let found_sizes = enabled_account_indexes
.indexes
.iter()
.filter_map(|index| {
accounts_index
.get_index_key_size(index, &index_key)
.map(|size| (rpc_account_index_from_account_index(index), size))
})
.collect::<HashMap<_, _>>();
// Note: Will return an empty HashMap if no keys are found.
if found_sizes.is_empty() {
debug!("get_secondary_index_key_size: key not found in the secondary index.");
}
Ok(found_sizes)
})
}
}
impl AdminRpcImpl {
@ -417,6 +478,14 @@ impl AdminRpcImpl {
}
}
fn rpc_account_index_from_account_index(account_index: &AccountIndex) -> RpcAccountIndex {
match account_index {
AccountIndex::ProgramId => RpcAccountIndex::ProgramId,
AccountIndex::SplTokenOwner => RpcAccountIndex::SplTokenOwner,
AccountIndex::SplTokenMint => RpcAccountIndex::SplTokenMint,
}
}
// Start the Admin RPC interface
pub fn run(ledger_path: &Path, metadata: AdminRpcRequestMetadata) {
let admin_rpc_path = admin_rpc_path(ledger_path);
@ -529,3 +598,417 @@ pub fn load_staked_nodes_overrides(
Err(format!("Staked nodes overrides provided '{path}' a non-existing file path.").into())
}
}
#[cfg(test)]
mod tests {
use {
super::*,
serde_json::Value,
solana_account_decoder::parse_token::spl_token_pubkey,
solana_core::tower_storage::NullTowerStorage,
solana_ledger::genesis_utils::{create_genesis_config, GenesisConfigInfo},
solana_rpc::rpc::create_validator_exit,
solana_runtime::{
accounts_index::AccountSecondaryIndexes,
bank::{Bank, BankTestConfig},
inline_spl_token,
},
solana_sdk::{
account::{Account, AccountSharedData},
system_program,
},
solana_streamer::socket::SocketAddrSpace,
spl_token_2022::{
solana_program::{program_option::COption, program_pack::Pack},
state::{Account as TokenAccount, AccountState as TokenAccountState, Mint},
},
std::{collections::HashSet, sync::atomic::AtomicBool},
};
#[derive(Default)]
struct TestConfig {
account_indexes: AccountSecondaryIndexes,
}
struct RpcHandler {
io: MetaIoHandler<AdminRpcRequestMetadata>,
meta: AdminRpcRequestMetadata,
bank_forks: Arc<RwLock<BankForks>>,
}
impl RpcHandler {
fn _start() -> Self {
Self::start_with_config(TestConfig::default())
}
fn start_with_config(config: TestConfig) -> Self {
let identity = Pubkey::new_unique();
let cluster_info = Arc::new(ClusterInfo::new(
ContactInfo {
id: identity,
..ContactInfo::default()
},
Arc::new(Keypair::new()),
SocketAddrSpace::Unspecified,
));
let exit = Arc::new(AtomicBool::new(false));
let validator_exit = create_validator_exit(&exit);
let (bank_forks, vote_keypair) = new_bank_forks_with_config(BankTestConfig {
secondary_indexes: config.account_indexes,
});
let vote_account = vote_keypair.pubkey();
let start_progress = Arc::new(RwLock::new(ValidatorStartProgress::default()));
let repair_whitelist = Arc::new(RwLock::new(HashSet::new()));
let meta = AdminRpcRequestMetadata {
rpc_addr: None,
start_time: SystemTime::now(),
start_progress,
validator_exit,
authorized_voter_keypairs: Arc::new(RwLock::new(vec![vote_keypair])),
tower_storage: Arc::new(NullTowerStorage {}),
post_init: Arc::new(RwLock::new(Some(AdminRpcRequestMetadataPostInit {
cluster_info,
bank_forks: bank_forks.clone(),
vote_account,
repair_whitelist,
}))),
staked_nodes_overrides: Arc::new(RwLock::new(HashMap::new())),
};
let mut io = MetaIoHandler::default();
io.extend_with(AdminRpcImpl.to_delegate());
Self {
io,
meta,
bank_forks,
}
}
fn root_bank(&self) -> Arc<Bank> {
self.bank_forks.read().unwrap().root_bank()
}
}
fn new_bank_forks_with_config(
config: BankTestConfig,
) -> (Arc<RwLock<BankForks>>, Arc<Keypair>) {
let GenesisConfigInfo {
genesis_config,
voting_keypair,
..
} = create_genesis_config(1_000_000_000);
let bank = Bank::new_for_tests_with_config(&genesis_config, config);
(
Arc::new(RwLock::new(BankForks::new(bank))),
Arc::new(voting_keypair),
)
}
#[test]
fn test_secondary_index_key_sizes() {
for secondary_index_enabled in [true, false] {
let account_indexes = if secondary_index_enabled {
AccountSecondaryIndexes {
keys: None,
indexes: HashSet::from([
AccountIndex::ProgramId,
AccountIndex::SplTokenMint,
AccountIndex::SplTokenOwner,
]),
}
} else {
AccountSecondaryIndexes::default()
};
// RPC & Bank Setup
let rpc = RpcHandler::start_with_config(TestConfig { account_indexes });
let bank = rpc.root_bank();
let RpcHandler { io, meta, .. } = rpc;
// Pubkeys
let token_account1_pubkey = Pubkey::new_unique();
let token_account2_pubkey = Pubkey::new_unique();
let token_account3_pubkey = Pubkey::new_unique();
let mint1_pubkey = Pubkey::new_unique();
let mint2_pubkey = Pubkey::new_unique();
let wallet1_pubkey = Pubkey::new_unique();
let wallet2_pubkey = Pubkey::new_unique();
let non_existent_pubkey = Pubkey::new_unique();
let delegate = spl_token_pubkey(&Pubkey::new_unique());
let mut num_default_spl_token_program_accounts = 0;
let mut num_default_system_program_accounts = 0;
if !secondary_index_enabled {
// Test first with no accounts added & no secondary indexes enabled:
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getSecondaryIndexKeySize","params":["{token_account1_pubkey}"]}}"#,
);
let res = io.handle_request_sync(&req, meta.clone());
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
let sizes: HashMap<RpcAccountIndex, usize> =
serde_json::from_value(result["result"].clone()).unwrap();
assert!(sizes.is_empty());
} else {
// Count SPL Token Program Default Accounts
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getSecondaryIndexKeySize","params":["{}"]}}"#,
inline_spl_token::id(),
);
let res = io.handle_request_sync(&req, meta.clone());
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
println!("{:?}", result);
let sizes: HashMap<RpcAccountIndex, usize> =
serde_json::from_value(result["result"].clone()).unwrap();
assert_eq!(sizes.len(), 1);
num_default_spl_token_program_accounts =
*sizes.get(&RpcAccountIndex::ProgramId).unwrap();
// Count System Program Default Accounts
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getSecondaryIndexKeySize","params":["{}"]}}"#,
system_program::id(),
);
let res = io.handle_request_sync(&req, meta.clone());
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
let sizes: HashMap<RpcAccountIndex, usize> =
serde_json::from_value(result["result"].clone()).unwrap();
assert_eq!(sizes.len(), 1);
num_default_system_program_accounts =
*sizes.get(&RpcAccountIndex::ProgramId).unwrap();
}
// Add 2 basic wallet accounts
let wallet1_account = AccountSharedData::from(Account {
lamports: 11111111,
owner: system_program::id(),
..Account::default()
});
bank.store_account(&wallet1_pubkey, &wallet1_account);
let wallet2_account = AccountSharedData::from(Account {
lamports: 11111111,
owner: system_program::id(),
..Account::default()
});
bank.store_account(&wallet2_pubkey, &wallet2_account);
// Add a token account
let mut account1_data = vec![0; TokenAccount::get_packed_len()];
let token_account1 = TokenAccount {
mint: spl_token_pubkey(&mint1_pubkey),
owner: spl_token_pubkey(&wallet1_pubkey),
delegate: COption::Some(delegate),
amount: 420,
state: TokenAccountState::Initialized,
is_native: COption::None,
delegated_amount: 30,
close_authority: COption::Some(spl_token_pubkey(&wallet1_pubkey)),
};
TokenAccount::pack(token_account1, &mut account1_data).unwrap();
let token_account1 = AccountSharedData::from(Account {
lamports: 111,
data: account1_data.to_vec(),
owner: inline_spl_token::id(),
..Account::default()
});
bank.store_account(&token_account1_pubkey, &token_account1);
// Add the mint
let mut mint1_data = vec![0; Mint::get_packed_len()];
let mint1_state = Mint {
mint_authority: COption::Some(spl_token_pubkey(&wallet1_pubkey)),
supply: 500,
decimals: 2,
is_initialized: true,
freeze_authority: COption::Some(spl_token_pubkey(&wallet1_pubkey)),
};
Mint::pack(mint1_state, &mut mint1_data).unwrap();
let mint_account1 = AccountSharedData::from(Account {
lamports: 222,
data: mint1_data.to_vec(),
owner: inline_spl_token::id(),
..Account::default()
});
bank.store_account(&mint1_pubkey, &mint_account1);
// Add another token account with the different owner, but same delegate, and mint
let mut account2_data = vec![0; TokenAccount::get_packed_len()];
let token_account2 = TokenAccount {
mint: spl_token_pubkey(&mint1_pubkey),
owner: spl_token_pubkey(&wallet2_pubkey),
delegate: COption::Some(delegate),
amount: 420,
state: TokenAccountState::Initialized,
is_native: COption::None,
delegated_amount: 30,
close_authority: COption::Some(spl_token_pubkey(&wallet2_pubkey)),
};
TokenAccount::pack(token_account2, &mut account2_data).unwrap();
let token_account2 = AccountSharedData::from(Account {
lamports: 333,
data: account2_data.to_vec(),
owner: inline_spl_token::id(),
..Account::default()
});
bank.store_account(&token_account2_pubkey, &token_account2);
// Add another token account with the same owner and delegate but different mint
let mut account3_data = vec![0; TokenAccount::get_packed_len()];
let token_account3 = TokenAccount {
mint: spl_token_pubkey(&mint2_pubkey),
owner: spl_token_pubkey(&wallet2_pubkey),
delegate: COption::Some(delegate),
amount: 42,
state: TokenAccountState::Initialized,
is_native: COption::None,
delegated_amount: 30,
close_authority: COption::Some(spl_token_pubkey(&wallet2_pubkey)),
};
TokenAccount::pack(token_account3, &mut account3_data).unwrap();
let token_account3 = AccountSharedData::from(Account {
lamports: 444,
data: account3_data.to_vec(),
owner: inline_spl_token::id(),
..Account::default()
});
bank.store_account(&token_account3_pubkey, &token_account3);
// Add the new mint
let mut mint2_data = vec![0; Mint::get_packed_len()];
let mint2_state = Mint {
mint_authority: COption::Some(spl_token_pubkey(&wallet2_pubkey)),
supply: 200,
decimals: 3,
is_initialized: true,
freeze_authority: COption::Some(spl_token_pubkey(&wallet2_pubkey)),
};
Mint::pack(mint2_state, &mut mint2_data).unwrap();
let mint_account2 = AccountSharedData::from(Account {
lamports: 555,
data: mint2_data.to_vec(),
owner: inline_spl_token::id(),
..Account::default()
});
bank.store_account(&mint2_pubkey, &mint_account2);
// Accounts should now look like the following:
//
// -----system_program------
// / \
// /-(owns) \-(owns)
// / \
// wallet1 ---wallet2---
// / / \
// /-(SPL::owns) /-(SPL::owns) \-(SPL::owns)
// / / \
// token_account1 token_account2 token_account3
// \ / /
// \-(SPL::mint) /-(SPL::mint) /-(SPL::mint)
// \ / /
// --mint_account1-- mint_account2
if secondary_index_enabled {
// ----------- Test for a non-existant key -----------
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getSecondaryIndexKeySize","params":["{non_existent_pubkey}"]}}"#,
);
let res = io.handle_request_sync(&req, meta.clone());
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
let sizes: HashMap<RpcAccountIndex, usize> =
serde_json::from_value(result["result"].clone()).unwrap();
assert!(sizes.is_empty());
// --------------- Test Queries ---------------
// 1) Wallet1 - Owns 1 SPL Token
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getSecondaryIndexKeySize","params":["{wallet1_pubkey}"]}}"#,
);
let res = io.handle_request_sync(&req, meta.clone());
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
let sizes: HashMap<RpcAccountIndex, usize> =
serde_json::from_value(result["result"].clone()).unwrap();
assert_eq!(sizes.len(), 1);
assert_eq!(*sizes.get(&RpcAccountIndex::SplTokenOwner).unwrap(), 1);
// 2) Wallet2 - Owns 2 SPL Tokens
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getSecondaryIndexKeySize","params":["{wallet2_pubkey}"]}}"#,
);
let res = io.handle_request_sync(&req, meta.clone());
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
let sizes: HashMap<RpcAccountIndex, usize> =
serde_json::from_value(result["result"].clone()).unwrap();
assert_eq!(sizes.len(), 1);
assert_eq!(*sizes.get(&RpcAccountIndex::SplTokenOwner).unwrap(), 2);
// 3) Mint1 - Is in 2 SPL Accounts
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getSecondaryIndexKeySize","params":["{mint1_pubkey}"]}}"#,
);
let res = io.handle_request_sync(&req, meta.clone());
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
let sizes: HashMap<RpcAccountIndex, usize> =
serde_json::from_value(result["result"].clone()).unwrap();
assert_eq!(sizes.len(), 1);
assert_eq!(*sizes.get(&RpcAccountIndex::SplTokenMint).unwrap(), 2);
// 4) Mint2 - Is in 1 SPL Account
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getSecondaryIndexKeySize","params":["{mint2_pubkey}"]}}"#,
);
let res = io.handle_request_sync(&req, meta.clone());
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
let sizes: HashMap<RpcAccountIndex, usize> =
serde_json::from_value(result["result"].clone()).unwrap();
assert_eq!(sizes.len(), 1);
assert_eq!(*sizes.get(&RpcAccountIndex::SplTokenMint).unwrap(), 1);
// 5) SPL Token Program Owns 6 Accounts - 1 Default, 5 created above.
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getSecondaryIndexKeySize","params":["{}"]}}"#,
inline_spl_token::id(),
);
let res = io.handle_request_sync(&req, meta.clone());
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
let sizes: HashMap<RpcAccountIndex, usize> =
serde_json::from_value(result["result"].clone()).unwrap();
assert_eq!(sizes.len(), 1);
assert_eq!(
*sizes.get(&RpcAccountIndex::ProgramId).unwrap(),
(num_default_spl_token_program_accounts + 5)
);
// 5) System Program Owns 4 Accounts + 2 Default, 2 created above.
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getSecondaryIndexKeySize","params":["{}"]}}"#,
system_program::id(),
);
let res = io.handle_request_sync(&req, meta.clone());
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
let sizes: HashMap<RpcAccountIndex, usize> =
serde_json::from_value(result["result"].clone()).unwrap();
assert_eq!(sizes.len(), 1);
assert_eq!(
*sizes.get(&RpcAccountIndex::ProgramId).unwrap(),
(num_default_system_program_accounts + 2)
);
} else {
// ------------ Secondary Indexes Disabled ------------
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getSecondaryIndexKeySize","params":["{token_account2_pubkey}"]}}"#,
);
let res = io.handle_request_sync(&req, meta.clone());
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
let sizes: HashMap<RpcAccountIndex, usize> =
serde_json::from_value(result["result"].clone()).unwrap();
assert!(sizes.is_empty());
}
}
}
}