diff --git a/Cargo.lock b/Cargo.lock index d4c6d859a..bd00814b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", ] diff --git a/rpc/src/rpc.rs b/rpc/src/rpc.rs index dfd57fe4f..c01c27465 100644 --- a/rpc/src/rpc.rs +++ b/rpc/src/rpc.rs @@ -114,14 +114,6 @@ type RpcCustomResult = std::result::Result; 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(bank: &Bank, value: T) -> RpcResponse { 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, - ) -> Result>> { - // 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::>(); - - // 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, @@ -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 { +pub fn verify_pubkey(input: &str) -> Result { input .parse() .map_err(|e| Error::invalid_params(format!("Invalid param: {e:?}"))) @@ -3155,16 +3102,6 @@ pub mod rpc_accounts_scan { config: Option, ) -> Result>>; - // 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, - ) -> Result>>; - #[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, - ) -> Result>> { - 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) -> Arc> { +pub fn create_validator_exit(exit: &Arc) -> Arc> { 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 = - 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 = - 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 = - 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 = - 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 = - 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 = - 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 = - 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 = - 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 = - 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 = - 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 = - 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() { diff --git a/validator/Cargo.toml b/validator/Cargo.toml index 89e9047e0..912ccf17f 100644 --- a/validator/Cargo.toml +++ b/validator/Cargo.toml @@ -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"] } diff --git a/validator/src/admin_rpc_service.rs b/validator/src/admin_rpc_service.rs index f7441235d..68d58e733 100644 --- a/validator/src/admin_rpc_service.rs +++ b/validator/src/admin_rpc_service.rs @@ -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) -> Result<()>; + + #[rpc(meta, name = "getSecondaryIndexKeySize")] + fn get_secondary_index_key_size( + &self, + meta: Self::Metadata, + pubkey_str: String, + ) -> Result>; } 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> { + 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::>(); + + // 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, + meta: AdminRpcRequestMetadata, + bank_forks: Arc>, + } + + 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 { + self.bank_forks.read().unwrap().root_bank() + } + } + + fn new_bank_forks_with_config( + config: BankTestConfig, + ) -> (Arc>, Arc) { + 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 = + 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 = + 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 = + 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 = + 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 = + 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 = + 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 = + 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 = + 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 = + 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 = + 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 = + serde_json::from_value(result["result"].clone()).unwrap(); + assert!(sizes.is_empty()); + } + } + } +}