diff --git a/client/src/rpc_custom_error.rs b/client/src/rpc_custom_error.rs index 5288515d7625a8..c7a962e17c3e86 100644 --- a/client/src/rpc_custom_error.rs +++ b/client/src/rpc_custom_error.rs @@ -15,6 +15,7 @@ pub const JSON_RPC_SERVER_ERROR_TRANSACTION_PRECOMPILE_VERIFICATION_FAILURE: i64 pub const JSON_RPC_SERVER_ERROR_SLOT_SKIPPED: i64 = -32007; pub const JSON_RPC_SERVER_ERROR_NO_SNAPSHOT: i64 = -32008; pub const JSON_RPC_SERVER_ERROR_LONG_TERM_STORAGE_SLOT_SKIPPED: i64 = -32009; +pub const JSON_RPC_SERVER_ERROR_KEY_EXCLUDED_FROM_SECONDARY_INDEX: i64 = -32010; pub enum RpcCustomError { BlockCleanedUp { @@ -40,6 +41,9 @@ pub enum RpcCustomError { LongTermStorageSlotSkipped { slot: Slot, }, + KeyExcludedFromSecondaryIndex { + index_key: String, + }, } #[derive(Debug, Serialize, Deserialize)] @@ -117,6 +121,17 @@ impl From for Error { message: format!("Slot {} was skipped, or missing in long-term storage", slot), data: None, }, + RpcCustomError::KeyExcludedFromSecondaryIndex { index_key } => Self { + code: ErrorCode::ServerError( + JSON_RPC_SERVER_ERROR_KEY_EXCLUDED_FROM_SECONDARY_INDEX, + ), + message: format!( + "{} excluded from account secondary indexes; \ + this RPC method unavailable for key", + index_key + ), + data: None, + }, } } } diff --git a/core/src/rpc.rs b/core/src/rpc.rs index 6dc85e41222444..ad0900e22dc988 100644 --- a/core/src/rpc.rs +++ b/core/src/rpc.rs @@ -46,7 +46,7 @@ use solana_metrics::inc_new_counter_info; use solana_perf::packet::PACKET_DATA_SIZE; use solana_runtime::{ accounts::AccountAddressFilter, - accounts_index::{AccountIndex, IndexKey}, + accounts_index::{AccountIndex, AccountSecondaryIndexes, IndexKey}, bank::Bank, bank_forks::{BankForks, SnapshotConfig}, commitment::{BlockCommitmentArray, BlockCommitmentCache, CommitmentSlots}, @@ -125,7 +125,7 @@ pub struct JsonRpcConfig { pub enable_bigtable_ledger_storage: bool, pub enable_bigtable_ledger_upload: bool, pub max_multiple_accounts: Option, - pub account_indexes: HashSet, + pub account_indexes: AccountSecondaryIndexes, pub rpc_threads: usize, pub rpc_bigtable_timeout: Option, pub minimal_api: bool, @@ -360,11 +360,11 @@ impl JsonRpcRequestProcessor { check_slice_and_encoding(&encoding, data_slice_config.is_some())?; let keyed_accounts = { if let Some(owner) = get_spl_token_owner_filter(program_id, &filters) { - self.get_filtered_spl_token_accounts_by_owner(&bank, &owner, filters) + self.get_filtered_spl_token_accounts_by_owner(&bank, &owner, filters)? } else if let Some(mint) = get_spl_token_mint_filter(program_id, &filters) { - self.get_filtered_spl_token_accounts_by_mint(&bank, &mint, filters) + self.get_filtered_spl_token_accounts_by_mint(&bank, &mint, filters)? } else { - self.get_filtered_program_accounts(&bank, program_id, filters) + self.get_filtered_program_accounts(&bank, program_id, filters)? } }; let result = @@ -1559,7 +1559,7 @@ impl JsonRpcRequestProcessor { )); } let mut token_balances: Vec = self - .get_filtered_spl_token_accounts_by_mint(&bank, &mint, vec![]) + .get_filtered_spl_token_accounts_by_mint(&bank, &mint, vec![])? .into_iter() .map(|(address, account)| { let amount = TokenAccount::unpack(&account.data()) @@ -1607,7 +1607,8 @@ impl JsonRpcRequestProcessor { })); } - let keyed_accounts = self.get_filtered_spl_token_accounts_by_owner(&bank, owner, filters); + let keyed_accounts = + self.get_filtered_spl_token_accounts_by_owner(&bank, owner, filters)?; let accounts = if encoding == UiAccountEncoding::JsonParsed { get_parsed_token_accounts(bank.clone(), keyed_accounts.into_iter()).collect() } else { @@ -1659,13 +1660,13 @@ impl JsonRpcRequestProcessor { ]; // Optional filter on Mint address, uses mint account index for scan let keyed_accounts = if let Some(mint) = mint { - self.get_filtered_spl_token_accounts_by_mint(&bank, &mint, filters) + self.get_filtered_spl_token_accounts_by_mint(&bank, &mint, filters)? } else { // Filter on Token Account state filters.push(RpcFilterType::DataSize( TokenAccount::get_packed_len() as u64 )); - self.get_filtered_program_accounts(&bank, &token_program_id, filters) + self.get_filtered_program_accounts(&bank, &token_program_id, filters)? }; let accounts = if encoding == UiAccountEncoding::JsonParsed { get_parsed_token_accounts(bank.clone(), keyed_accounts.into_iter()).collect() @@ -1693,7 +1694,7 @@ impl JsonRpcRequestProcessor { bank: &Arc, program_id: &Pubkey, filters: Vec, - ) -> Vec<(Pubkey, AccountSharedData)> { + ) -> Result> { let filter_closure = |account: &AccountSharedData| { filters.iter().all(|filter_type| match filter_type { RpcFilterType::DataSize(size) => account.data().len() as u64 == *size, @@ -1705,16 +1706,24 @@ impl JsonRpcRequestProcessor { .account_indexes .contains(&AccountIndex::ProgramId) { - bank.get_filtered_indexed_accounts(&IndexKey::ProgramId(*program_id), |account| { - // The program-id account index checks for Account owner on inclusion. However, due - // to the current AccountsDb implementation, an account may remain in storage as a - // zero-lamport AccountSharedData::Default() after being wiped and reinitialized in later - // updates. We include the redundant filters here to avoid returning these - // accounts. - account.owner() == program_id && filter_closure(account) - }) + if !self.config.account_indexes.include_key(program_id) { + return Err(RpcCustomError::KeyExcludedFromSecondaryIndex { + index_key: program_id.to_string(), + } + .into()); + } + Ok( + bank.get_filtered_indexed_accounts(&IndexKey::ProgramId(*program_id), |account| { + // The program-id account index checks for Account owner on inclusion. However, due + // to the current AccountsDb implementation, an account may remain in storage as a + // zero-lamport AccountSharedData::Default() after being wiped and reinitialized in later + // updates. We include the redundant filters here to avoid returning these + // accounts. + account.owner() == program_id && filter_closure(account) + }), + ) } else { - bank.get_filtered_program_accounts(program_id, filter_closure) + Ok(bank.get_filtered_program_accounts(program_id, filter_closure)) } } @@ -1724,7 +1733,7 @@ impl JsonRpcRequestProcessor { bank: &Arc, owner_key: &Pubkey, mut filters: Vec, - ) -> Vec<(Pubkey, AccountSharedData)> { + ) -> Result> { // The by-owner accounts index checks for Token Account state and Owner address on // inclusion. However, due to the current AccountsDb implementation, an account may remain // in storage as a zero-lamport AccountSharedData::Default() after being wiped and reinitialized in @@ -1746,13 +1755,22 @@ impl JsonRpcRequestProcessor { .account_indexes .contains(&AccountIndex::SplTokenOwner) { - bank.get_filtered_indexed_accounts(&IndexKey::SplTokenOwner(*owner_key), |account| { - account.owner() == &spl_token_id_v2_0() - && filters.iter().all(|filter_type| match filter_type { - RpcFilterType::DataSize(size) => account.data().len() as u64 == *size, - RpcFilterType::Memcmp(compare) => compare.bytes_match(&account.data()), - }) - }) + if !self.config.account_indexes.include_key(owner_key) { + return Err(RpcCustomError::KeyExcludedFromSecondaryIndex { + index_key: owner_key.to_string(), + } + .into()); + } + Ok(bank.get_filtered_indexed_accounts( + &IndexKey::SplTokenOwner(*owner_key), + |account| { + account.owner() == &spl_token_id_v2_0() + && filters.iter().all(|filter_type| match filter_type { + RpcFilterType::DataSize(size) => account.data().len() as u64 == *size, + RpcFilterType::Memcmp(compare) => compare.bytes_match(&account.data()), + }) + }, + )) } else { self.get_filtered_program_accounts(bank, &spl_token_id_v2_0(), filters) } @@ -1764,7 +1782,7 @@ impl JsonRpcRequestProcessor { bank: &Arc, mint_key: &Pubkey, mut filters: Vec, - ) -> Vec<(Pubkey, AccountSharedData)> { + ) -> Result> { // The by-mint accounts index checks for Token Account state and Mint address on inclusion. // However, due to the current AccountsDb implementation, an account may remain in storage // as be zero-lamport AccountSharedData::Default() after being wiped and reinitialized in later @@ -1785,13 +1803,21 @@ impl JsonRpcRequestProcessor { .account_indexes .contains(&AccountIndex::SplTokenMint) { - bank.get_filtered_indexed_accounts(&IndexKey::SplTokenMint(*mint_key), |account| { - account.owner() == &spl_token_id_v2_0() - && filters.iter().all(|filter_type| match filter_type { - RpcFilterType::DataSize(size) => account.data().len() as u64 == *size, - RpcFilterType::Memcmp(compare) => compare.bytes_match(&account.data()), - }) - }) + if !self.config.account_indexes.include_key(mint_key) { + return Err(RpcCustomError::KeyExcludedFromSecondaryIndex { + index_key: mint_key.to_string(), + } + .into()); + } + Ok( + bank.get_filtered_indexed_accounts(&IndexKey::SplTokenMint(*mint_key), |account| { + account.owner() == &spl_token_id_v2_0() + && filters.iter().all(|filter_type| match filter_type { + RpcFilterType::DataSize(size) => account.data().len() as u64 == *size, + RpcFilterType::Memcmp(compare) => compare.bytes_match(&account.data()), + }) + }), + ) } else { self.get_filtered_program_accounts(bank, &spl_token_id_v2_0(), filters) } diff --git a/runtime/src/accounts.rs b/runtime/src/accounts.rs index e33268013fd42a..56d122cd57e5c7 100644 --- a/runtime/src/accounts.rs +++ b/runtime/src/accounts.rs @@ -715,6 +715,10 @@ impl Accounts { .0 } + pub fn account_indexes_include_key(&self, key: &Pubkey) -> bool { + self.accounts_db.account_indexes.include_key(key) + } + pub fn load_all(&self, ancestors: &Ancestors) -> Vec<(Pubkey, AccountSharedData, Slot)> { self.accounts_db.scan_accounts( ancestors, diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index f08dc137b56dc6..f932d09e0ffebd 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -4246,6 +4246,10 @@ impl Bank { .load_by_index_key_with_filter(&self.ancestors, index_key, filter) } + pub fn account_indexes_include_key(&self, key: &Pubkey) -> bool { + self.rc.accounts.account_indexes_include_key(key) + } + pub fn get_all_accounts_with_modified_slots(&self) -> Vec<(Pubkey, AccountSharedData, Slot)> { self.rc.accounts.load_all(&self.ancestors) } diff --git a/validator/src/main.rs b/validator/src/main.rs index 92dd51ed5c8829..bcc8d1487a3c97 100644 --- a/validator/src/main.rs +++ b/validator/src/main.rs @@ -2116,7 +2116,7 @@ pub fn main() { rpc_bigtable_timeout: value_t!(matches, "rpc_bigtable_timeout", u64) .ok() .map(Duration::from_secs), - account_indexes: account_indexes.indexes.clone(), + account_indexes: account_indexes.clone(), }, rpc_addrs: value_t!(matches, "rpc_port", u16).ok().map(|rpc_port| { (