Return error for excluded secondary-index keys (#17193)

* Add runtime helpers to check secondary indexes for key

* Add custom rpc error

* Check secondary-index key inclusion in rpc

* Clone complete AccountSecondaryIndexes into rpc to avoid bank query
This commit is contained in:
Tyera Eulberg 2021-05-13 15:04:21 -06:00 committed by GitHub
parent 3dbc7744ab
commit 27004f1b76
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 85 additions and 36 deletions

View File

@ -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_SLOT_SKIPPED: i64 = -32007;
pub const JSON_RPC_SERVER_ERROR_NO_SNAPSHOT: i64 = -32008; 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_LONG_TERM_STORAGE_SLOT_SKIPPED: i64 = -32009;
pub const JSON_RPC_SERVER_ERROR_KEY_EXCLUDED_FROM_SECONDARY_INDEX: i64 = -32010;
pub enum RpcCustomError { pub enum RpcCustomError {
BlockCleanedUp { BlockCleanedUp {
@ -40,6 +41,9 @@ pub enum RpcCustomError {
LongTermStorageSlotSkipped { LongTermStorageSlotSkipped {
slot: Slot, slot: Slot,
}, },
KeyExcludedFromSecondaryIndex {
index_key: String,
},
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
@ -117,6 +121,17 @@ impl From<RpcCustomError> for Error {
message: format!("Slot {} was skipped, or missing in long-term storage", slot), message: format!("Slot {} was skipped, or missing in long-term storage", slot),
data: None, 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,
},
} }
} }
} }

View File

@ -46,7 +46,7 @@ use solana_metrics::inc_new_counter_info;
use solana_perf::packet::PACKET_DATA_SIZE; use solana_perf::packet::PACKET_DATA_SIZE;
use solana_runtime::{ use solana_runtime::{
accounts::AccountAddressFilter, accounts::AccountAddressFilter,
accounts_index::{AccountIndex, IndexKey}, accounts_index::{AccountIndex, AccountSecondaryIndexes, IndexKey},
bank::Bank, bank::Bank,
bank_forks::{BankForks, SnapshotConfig}, bank_forks::{BankForks, SnapshotConfig},
commitment::{BlockCommitmentArray, BlockCommitmentCache, CommitmentSlots}, commitment::{BlockCommitmentArray, BlockCommitmentCache, CommitmentSlots},
@ -125,7 +125,7 @@ pub struct JsonRpcConfig {
pub enable_bigtable_ledger_storage: bool, pub enable_bigtable_ledger_storage: bool,
pub enable_bigtable_ledger_upload: bool, pub enable_bigtable_ledger_upload: bool,
pub max_multiple_accounts: Option<usize>, pub max_multiple_accounts: Option<usize>,
pub account_indexes: HashSet<AccountIndex>, pub account_indexes: AccountSecondaryIndexes,
pub rpc_threads: usize, pub rpc_threads: usize,
pub rpc_bigtable_timeout: Option<Duration>, pub rpc_bigtable_timeout: Option<Duration>,
pub minimal_api: bool, pub minimal_api: bool,
@ -360,11 +360,11 @@ impl JsonRpcRequestProcessor {
check_slice_and_encoding(&encoding, data_slice_config.is_some())?; check_slice_and_encoding(&encoding, data_slice_config.is_some())?;
let keyed_accounts = { let keyed_accounts = {
if let Some(owner) = get_spl_token_owner_filter(program_id, &filters) { 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) { } 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 { } else {
self.get_filtered_program_accounts(&bank, program_id, filters) self.get_filtered_program_accounts(&bank, program_id, filters)?
} }
}; };
let result = let result =
@ -1559,7 +1559,7 @@ impl JsonRpcRequestProcessor {
)); ));
} }
let mut token_balances: Vec<RpcTokenAccountBalance> = self let mut token_balances: Vec<RpcTokenAccountBalance> = self
.get_filtered_spl_token_accounts_by_mint(&bank, &mint, vec![]) .get_filtered_spl_token_accounts_by_mint(&bank, &mint, vec![])?
.into_iter() .into_iter()
.map(|(address, account)| { .map(|(address, account)| {
let amount = TokenAccount::unpack(&account.data()) 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 { let accounts = if encoding == UiAccountEncoding::JsonParsed {
get_parsed_token_accounts(bank.clone(), keyed_accounts.into_iter()).collect() get_parsed_token_accounts(bank.clone(), keyed_accounts.into_iter()).collect()
} else { } else {
@ -1659,13 +1660,13 @@ impl JsonRpcRequestProcessor {
]; ];
// Optional filter on Mint address, uses mint account index for scan // Optional filter on Mint address, uses mint account index for scan
let keyed_accounts = if let Some(mint) = mint { 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 { } else {
// Filter on Token Account state // Filter on Token Account state
filters.push(RpcFilterType::DataSize( filters.push(RpcFilterType::DataSize(
TokenAccount::get_packed_len() as u64 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 { let accounts = if encoding == UiAccountEncoding::JsonParsed {
get_parsed_token_accounts(bank.clone(), keyed_accounts.into_iter()).collect() get_parsed_token_accounts(bank.clone(), keyed_accounts.into_iter()).collect()
@ -1693,7 +1694,7 @@ impl JsonRpcRequestProcessor {
bank: &Arc<Bank>, bank: &Arc<Bank>,
program_id: &Pubkey, program_id: &Pubkey,
filters: Vec<RpcFilterType>, filters: Vec<RpcFilterType>,
) -> Vec<(Pubkey, AccountSharedData)> { ) -> Result<Vec<(Pubkey, AccountSharedData)>> {
let filter_closure = |account: &AccountSharedData| { let filter_closure = |account: &AccountSharedData| {
filters.iter().all(|filter_type| match filter_type { filters.iter().all(|filter_type| match filter_type {
RpcFilterType::DataSize(size) => account.data().len() as u64 == *size, RpcFilterType::DataSize(size) => account.data().len() as u64 == *size,
@ -1705,6 +1706,13 @@ impl JsonRpcRequestProcessor {
.account_indexes .account_indexes
.contains(&AccountIndex::ProgramId) .contains(&AccountIndex::ProgramId)
{ {
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| { bank.get_filtered_indexed_accounts(&IndexKey::ProgramId(*program_id), |account| {
// The program-id account index checks for Account owner on inclusion. However, due // 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 // to the current AccountsDb implementation, an account may remain in storage as a
@ -1712,9 +1720,10 @@ impl JsonRpcRequestProcessor {
// updates. We include the redundant filters here to avoid returning these // updates. We include the redundant filters here to avoid returning these
// accounts. // accounts.
account.owner() == program_id && filter_closure(account) account.owner() == program_id && filter_closure(account)
}) }),
)
} else { } 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<Bank>, bank: &Arc<Bank>,
owner_key: &Pubkey, owner_key: &Pubkey,
mut filters: Vec<RpcFilterType>, mut filters: Vec<RpcFilterType>,
) -> Vec<(Pubkey, AccountSharedData)> { ) -> Result<Vec<(Pubkey, AccountSharedData)>> {
// The by-owner accounts index checks for Token Account state and Owner address on // 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 // 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 // in storage as a zero-lamport AccountSharedData::Default() after being wiped and reinitialized in
@ -1746,13 +1755,22 @@ impl JsonRpcRequestProcessor {
.account_indexes .account_indexes
.contains(&AccountIndex::SplTokenOwner) .contains(&AccountIndex::SplTokenOwner)
{ {
bank.get_filtered_indexed_accounts(&IndexKey::SplTokenOwner(*owner_key), |account| { 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() account.owner() == &spl_token_id_v2_0()
&& filters.iter().all(|filter_type| match filter_type { && filters.iter().all(|filter_type| match filter_type {
RpcFilterType::DataSize(size) => account.data().len() as u64 == *size, RpcFilterType::DataSize(size) => account.data().len() as u64 == *size,
RpcFilterType::Memcmp(compare) => compare.bytes_match(&account.data()), RpcFilterType::Memcmp(compare) => compare.bytes_match(&account.data()),
}) })
}) },
))
} else { } else {
self.get_filtered_program_accounts(bank, &spl_token_id_v2_0(), filters) self.get_filtered_program_accounts(bank, &spl_token_id_v2_0(), filters)
} }
@ -1764,7 +1782,7 @@ impl JsonRpcRequestProcessor {
bank: &Arc<Bank>, bank: &Arc<Bank>,
mint_key: &Pubkey, mint_key: &Pubkey,
mut filters: Vec<RpcFilterType>, mut filters: Vec<RpcFilterType>,
) -> Vec<(Pubkey, AccountSharedData)> { ) -> Result<Vec<(Pubkey, AccountSharedData)>> {
// The by-mint accounts index checks for Token Account state and Mint address on inclusion. // 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 // 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 // as be zero-lamport AccountSharedData::Default() after being wiped and reinitialized in later
@ -1785,13 +1803,21 @@ impl JsonRpcRequestProcessor {
.account_indexes .account_indexes
.contains(&AccountIndex::SplTokenMint) .contains(&AccountIndex::SplTokenMint)
{ {
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| { bank.get_filtered_indexed_accounts(&IndexKey::SplTokenMint(*mint_key), |account| {
account.owner() == &spl_token_id_v2_0() account.owner() == &spl_token_id_v2_0()
&& filters.iter().all(|filter_type| match filter_type { && filters.iter().all(|filter_type| match filter_type {
RpcFilterType::DataSize(size) => account.data().len() as u64 == *size, RpcFilterType::DataSize(size) => account.data().len() as u64 == *size,
RpcFilterType::Memcmp(compare) => compare.bytes_match(&account.data()), RpcFilterType::Memcmp(compare) => compare.bytes_match(&account.data()),
}) })
}) }),
)
} else { } else {
self.get_filtered_program_accounts(bank, &spl_token_id_v2_0(), filters) self.get_filtered_program_accounts(bank, &spl_token_id_v2_0(), filters)
} }

View File

@ -715,6 +715,10 @@ impl Accounts {
.0 .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)> { pub fn load_all(&self, ancestors: &Ancestors) -> Vec<(Pubkey, AccountSharedData, Slot)> {
self.accounts_db.scan_accounts( self.accounts_db.scan_accounts(
ancestors, ancestors,

View File

@ -4246,6 +4246,10 @@ impl Bank {
.load_by_index_key_with_filter(&self.ancestors, index_key, filter) .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)> { pub fn get_all_accounts_with_modified_slots(&self) -> Vec<(Pubkey, AccountSharedData, Slot)> {
self.rc.accounts.load_all(&self.ancestors) self.rc.accounts.load_all(&self.ancestors)
} }

View File

@ -2116,7 +2116,7 @@ pub fn main() {
rpc_bigtable_timeout: value_t!(matches, "rpc_bigtable_timeout", u64) rpc_bigtable_timeout: value_t!(matches, "rpc_bigtable_timeout", u64)
.ok() .ok()
.map(Duration::from_secs), .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| { rpc_addrs: value_t!(matches, "rpc_port", u16).ok().map(|rpc_port| {
( (