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:
parent
3dbc7744ab
commit
27004f1b76
|
@ -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,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,16 +1706,24 @@ impl JsonRpcRequestProcessor {
|
||||||
.account_indexes
|
.account_indexes
|
||||||
.contains(&AccountIndex::ProgramId)
|
.contains(&AccountIndex::ProgramId)
|
||||||
{
|
{
|
||||||
bank.get_filtered_indexed_accounts(&IndexKey::ProgramId(*program_id), |account| {
|
if !self.config.account_indexes.include_key(program_id) {
|
||||||
// The program-id account index checks for Account owner on inclusion. However, due
|
return Err(RpcCustomError::KeyExcludedFromSecondaryIndex {
|
||||||
// to the current AccountsDb implementation, an account may remain in storage as a
|
index_key: program_id.to_string(),
|
||||||
// zero-lamport AccountSharedData::Default() after being wiped and reinitialized in later
|
}
|
||||||
// updates. We include the redundant filters here to avoid returning these
|
.into());
|
||||||
// accounts.
|
}
|
||||||
account.owner() == program_id && filter_closure(account)
|
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 {
|
} 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) {
|
||||||
account.owner() == &spl_token_id_v2_0()
|
return Err(RpcCustomError::KeyExcludedFromSecondaryIndex {
|
||||||
&& filters.iter().all(|filter_type| match filter_type {
|
index_key: owner_key.to_string(),
|
||||||
RpcFilterType::DataSize(size) => account.data().len() as u64 == *size,
|
}
|
||||||
RpcFilterType::Memcmp(compare) => compare.bytes_match(&account.data()),
|
.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 {
|
} 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)
|
||||||
{
|
{
|
||||||
bank.get_filtered_indexed_accounts(&IndexKey::SplTokenMint(*mint_key), |account| {
|
if !self.config.account_indexes.include_key(mint_key) {
|
||||||
account.owner() == &spl_token_id_v2_0()
|
return Err(RpcCustomError::KeyExcludedFromSecondaryIndex {
|
||||||
&& filters.iter().all(|filter_type| match filter_type {
|
index_key: mint_key.to_string(),
|
||||||
RpcFilterType::DataSize(size) => account.data().len() as u64 == *size,
|
}
|
||||||
RpcFilterType::Memcmp(compare) => compare.bytes_match(&account.data()),
|
.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 {
|
} 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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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| {
|
||||||
(
|
(
|
||||||
|
|
Loading…
Reference in New Issue