From 86d465c53137978b464e01c1e1155b9f58b166cb Mon Sep 17 00:00:00 2001 From: Michael Vines Date: Tue, 25 Jan 2022 20:32:21 -0800 Subject: [PATCH] Prepare RPC subsystem for multiple SPL Token program ids --- account-decoder/src/parse_account_data.rs | 7 +-- account-decoder/src/parse_token.rs | 17 ++++++- rpc/src/parsed_token_accounts.rs | 9 +++- rpc/src/rpc.rs | 53 +++++++++++++-------- rpc/src/rpc_subscriptions.rs | 33 +++++++------ transaction-status/src/parse_instruction.rs | 7 +-- transaction-status/src/token_balances.rs | 12 ++--- 7 files changed, 85 insertions(+), 53 deletions(-) diff --git a/account-decoder/src/parse_account_data.rs b/account-decoder/src/parse_account_data.rs index 35fff127e0..6a9cd0b6c2 100644 --- a/account-decoder/src/parse_account_data.rs +++ b/account-decoder/src/parse_account_data.rs @@ -5,7 +5,7 @@ use { parse_nonce::parse_nonce, parse_stake::parse_stake, parse_sysvar::parse_sysvar, - parse_token::{parse_token, spl_token_id}, + parse_token::{parse_token, spl_token_ids}, parse_vote::parse_vote, }, inflector::Inflector, @@ -21,7 +21,6 @@ lazy_static! { static ref STAKE_PROGRAM_ID: Pubkey = stake::program::id(); static ref SYSTEM_PROGRAM_ID: Pubkey = system_program::id(); static ref SYSVAR_PROGRAM_ID: Pubkey = sysvar::id(); - static ref TOKEN_PROGRAM_ID: Pubkey = spl_token_id(); static ref VOTE_PROGRAM_ID: Pubkey = solana_vote_program::id(); pub static ref PARSABLE_PROGRAM_IDS: HashMap = { let mut m = HashMap::new(); @@ -31,7 +30,9 @@ lazy_static! { ); m.insert(*CONFIG_PROGRAM_ID, ParsableAccount::Config); m.insert(*SYSTEM_PROGRAM_ID, ParsableAccount::Nonce); - m.insert(*TOKEN_PROGRAM_ID, ParsableAccount::SplToken); + for spl_token_id in spl_token_ids() { + m.insert(spl_token_id, ParsableAccount::SplToken); + } m.insert(*STAKE_PROGRAM_ID, ParsableAccount::Stake); m.insert(*SYSVAR_PROGRAM_ID, ParsableAccount::Sysvar); m.insert(*VOTE_PROGRAM_ID, ParsableAccount::Vote); diff --git a/account-decoder/src/parse_token.rs b/account-decoder/src/parse_token.rs index b733f5746e..638b80a142 100644 --- a/account-decoder/src/parse_token.rs +++ b/account-decoder/src/parse_token.rs @@ -15,16 +15,31 @@ use { // A helper function to convert spl_token::id() as spl_sdk::pubkey::Pubkey to // solana_sdk::pubkey::Pubkey -pub fn spl_token_id() -> Pubkey { +fn spl_token_id() -> Pubkey { Pubkey::new_from_array(spl_token::id().to_bytes()) } +// Returns all known SPL Token program ids +pub fn spl_token_ids() -> Vec { + vec![spl_token_id()] +} + +// Check if the provided program id as a known SPL Token program id +pub fn is_known_spl_token_id(program_id: &Pubkey) -> bool { + *program_id == spl_token_id() +} + // A helper function to convert spl_token::native_mint::id() as spl_sdk::pubkey::Pubkey to // solana_sdk::pubkey::Pubkey pub fn spl_token_native_mint() -> Pubkey { Pubkey::new_from_array(spl_token::native_mint::id().to_bytes()) } +// The program id of the `spl_token_native_mint` account +pub fn spl_token_native_mint_program_id() -> Pubkey { + spl_token_id() +} + // A helper function to convert a solana_sdk::pubkey::Pubkey to spl_sdk::pubkey::Pubkey pub fn spl_token_pubkey(pubkey: &Pubkey) -> SplTokenPubkey { SplTokenPubkey::new_from_array(pubkey.to_bytes()) diff --git a/rpc/src/parsed_token_accounts.rs b/rpc/src/parsed_token_accounts.rs index ab0342de6c..6ae63d546e 100644 --- a/rpc/src/parsed_token_accounts.rs +++ b/rpc/src/parsed_token_accounts.rs @@ -2,7 +2,9 @@ use { jsonrpc_core::{Error, Result}, solana_account_decoder::{ parse_account_data::AccountAdditionalData, - parse_token::{get_token_account_mint, spl_token_id, spl_token_native_mint}, + parse_token::{ + get_token_account_mint, spl_token_native_mint, spl_token_native_mint_program_id, + }, UiAccount, UiAccountData, UiAccountEncoding, }, solana_client::rpc_response::RpcKeyedAccount, @@ -75,7 +77,10 @@ where /// program_id) and decimals pub fn get_mint_owner_and_decimals(bank: &Arc, mint: &Pubkey) -> Result<(Pubkey, u8)> { if mint == &spl_token_native_mint() { - Ok((spl_token_id(), spl_token::native_mint::DECIMALS)) + Ok(( + spl_token_native_mint_program_id(), + spl_token::native_mint::DECIMALS, + )) } else { let mint_account = bank.get_account(mint).ok_or_else(|| { Error::invalid_params("Invalid param: could not find mint".to_string()) diff --git a/rpc/src/rpc.rs b/rpc/src/rpc.rs index 84f9f98294..9c334b79f8 100644 --- a/rpc/src/rpc.rs +++ b/rpc/src/rpc.rs @@ -11,7 +11,7 @@ use { jsonrpc_derive::rpc, serde::{Deserialize, Serialize}, solana_account_decoder::{ - parse_token::{spl_token_id, token_amount_to_ui_amount, UiTokenAmount}, + parse_token::{is_known_spl_token_id, token_amount_to_ui_amount, UiTokenAmount}, UiAccount, UiAccountEncoding, UiDataSliceConfig, MAX_BASE58_BYTES, }, solana_client::{ @@ -405,14 +405,15 @@ impl JsonRpcRequestProcessor { optimize_filters(&mut filters); 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, program_id, &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, program_id, &mint, filters)? } else { self.get_filtered_program_accounts(&bank, program_id, filters)? } }; - let accounts = if program_id == &spl_token_id() && encoding == UiAccountEncoding::JsonParsed + let accounts = if is_known_spl_token_id(program_id) + && encoding == UiAccountEncoding::JsonParsed { get_parsed_token_accounts(bank.clone(), keyed_accounts.into_iter()).collect() } else { @@ -1709,7 +1710,7 @@ impl JsonRpcRequestProcessor { Error::invalid_params("Invalid param: could not find account".to_string()) })?; - if account.owner() != &spl_token_id() { + if !is_known_spl_token_id(account.owner()) { return Err(Error::invalid_params( "Invalid param: not a Token account".to_string(), )); @@ -1732,7 +1733,7 @@ impl JsonRpcRequestProcessor { let mint_account = bank.get_account(mint).ok_or_else(|| { Error::invalid_params("Invalid param: could not find account".to_string()) })?; - if mint_account.owner() != &spl_token_id() { + if !is_known_spl_token_id(mint_account.owner()) { return Err(Error::invalid_params( "Invalid param: not a Token mint".to_string(), )); @@ -1752,13 +1753,13 @@ impl JsonRpcRequestProcessor { ) -> Result>> { let bank = self.bank(commitment); let (mint_owner, decimals) = get_mint_owner_and_decimals(&bank, mint)?; - if mint_owner != spl_token_id() { + if !is_known_spl_token_id(&mint_owner) { return Err(Error::invalid_params( "Invalid param: not a Token mint".to_string(), )); } 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_owner, mint, vec![])? .into_iter() .map(|(address, account)| { let amount = TokenAccount::unpack(account.data()) @@ -1794,7 +1795,7 @@ impl JsonRpcRequestProcessor { let encoding = config.encoding.unwrap_or(UiAccountEncoding::Binary); let data_slice_config = config.data_slice; check_slice_and_encoding(&encoding, data_slice_config.is_some())?; - let (_, mint) = get_token_program_id_and_mint(&bank, token_account_filter)?; + let (token_program_id, mint) = get_token_program_id_and_mint(&bank, token_account_filter)?; let mut filters = vec![]; if let Some(mint) = mint { @@ -1806,8 +1807,12 @@ 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, + &token_program_id, + owner, + filters, + )?; let accounts = if encoding == UiAccountEncoding::JsonParsed { get_parsed_token_accounts(bank.clone(), keyed_accounts.into_iter()).collect() } else { @@ -1853,7 +1858,7 @@ 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, &token_program_id, &mint, filters)? } else { // Filter on Token Account state filters.push(RpcFilterType::DataSize( @@ -1932,6 +1937,7 @@ impl JsonRpcRequestProcessor { fn get_filtered_spl_token_accounts_by_owner( &self, bank: &Arc, + program_id: &Pubkey, owner_key: &Pubkey, mut filters: Vec, ) -> RpcCustomResult> { @@ -1965,7 +1971,7 @@ impl JsonRpcRequestProcessor { .get_filtered_indexed_accounts( &IndexKey::SplTokenOwner(*owner_key), |account| { - account.owner() == &spl_token_id() + account.owner() == program_id && filters.iter().all(|filter_type| match filter_type { RpcFilterType::DataSize(size) => { account.data().len() as u64 == *size @@ -1982,7 +1988,7 @@ impl JsonRpcRequestProcessor { message: e.to_string(), })?) } else { - self.get_filtered_program_accounts(bank, &spl_token_id(), filters) + self.get_filtered_program_accounts(bank, program_id, filters) } } @@ -1990,6 +1996,7 @@ impl JsonRpcRequestProcessor { fn get_filtered_spl_token_accounts_by_mint( &self, bank: &Arc, + program_id: &Pubkey, mint_key: &Pubkey, mut filters: Vec, ) -> RpcCustomResult> { @@ -2022,7 +2029,7 @@ impl JsonRpcRequestProcessor { .get_filtered_indexed_accounts( &IndexKey::SplTokenMint(*mint_key), |account| { - account.owner() == &spl_token_id() + account.owner() == program_id && filters.iter().all(|filter_type| match filter_type { RpcFilterType::DataSize(size) => { account.data().len() as u64 == *size @@ -2039,7 +2046,7 @@ impl JsonRpcRequestProcessor { message: e.to_string(), })?) } else { - self.get_filtered_program_accounts(bank, &spl_token_id(), filters) + self.get_filtered_program_accounts(bank, program_id, filters) } } @@ -2217,7 +2224,7 @@ fn get_encoded_account( ) -> Result> { match bank.get_account(pubkey) { Some(account) => { - let response = if account.owner() == &spl_token_id() + let response = if is_known_spl_token_id(account.owner()) && encoding == UiAccountEncoding::JsonParsed { get_parsed_token_account(bank.clone(), pubkey, account) @@ -2257,7 +2264,7 @@ fn encode_account( /// NOTE: `optimize_filters()` should almost always be called before using this method because of /// the strict match on `MemcmpEncodedBytes::Bytes`. fn get_spl_token_owner_filter(program_id: &Pubkey, filters: &[RpcFilterType]) -> Option { - if program_id != &spl_token_id() { + if !is_known_spl_token_id(program_id) { return None; } let mut data_size_filter: Option = None; @@ -2299,7 +2306,7 @@ fn get_spl_token_owner_filter(program_id: &Pubkey, filters: &[RpcFilterType]) -> /// NOTE: `optimize_filters()` should almost always be called before using this method because of /// the strict match on `MemcmpEncodedBytes::Bytes`. fn get_spl_token_mint_filter(program_id: &Pubkey, filters: &[RpcFilterType]) -> Option { - if program_id != &spl_token_id() { + if !is_known_spl_token_id(program_id) { return None; } let mut data_size_filter: Option = None; @@ -2345,7 +2352,7 @@ fn get_token_program_id_and_mint( match token_account_filter { TokenAccountsFilter::Mint(mint) => { let (mint_owner, _) = get_mint_owner_and_decimals(bank, &mint)?; - if mint_owner != spl_token_id() { + if !is_known_spl_token_id(&mint_owner) { return Err(Error::invalid_params( "Invalid param: not a Token mint".to_string(), )); @@ -2353,7 +2360,7 @@ fn get_token_program_id_and_mint( Ok((mint_owner, Some(mint))) } TokenAccountsFilter::ProgramId(program_id) => { - if program_id == spl_token_id() { + if is_known_spl_token_id(&program_id) { Ok((program_id, None)) } else { Err(Error::invalid_params( @@ -4422,6 +4429,10 @@ pub mod tests { std::collections::HashMap, }; + fn spl_token_id() -> Pubkey { + solana_account_decoder::parse_token::spl_token_ids()[0] + } + const TEST_MINT_LAMPORTS: u64 = 1_000_000; const TEST_SLOTS_PER_EPOCH: u64 = DELINQUENT_VALIDATOR_SLOT_DISTANCE + 1; diff --git a/rpc/src/rpc_subscriptions.rs b/rpc/src/rpc_subscriptions.rs index ae21692e26..f81b3392fc 100644 --- a/rpc/src/rpc_subscriptions.rs +++ b/rpc/src/rpc_subscriptions.rs @@ -14,7 +14,7 @@ use { crossbeam_channel::{Receiver, RecvTimeoutError, SendError, Sender}, rayon::prelude::*, serde::Serialize, - solana_account_decoder::{parse_token::spl_token_id, UiAccount, UiAccountEncoding}, + solana_account_decoder::{parse_token::is_known_spl_token_id, UiAccount, UiAccountEncoding}, solana_client::{ rpc_filter::RpcFilterType, rpc_response::{ @@ -330,7 +330,9 @@ fn filter_account_result( // If last_modified_slot < last_notified_slot this means that we last notified for a fork // and should notify that the account state has been reverted. let results: Box> = if last_modified_slot != last_notified_slot { - if account.owner() == &spl_token_id() && params.encoding == UiAccountEncoding::JsonParsed { + if is_known_spl_token_id(account.owner()) + && params.encoding == UiAccountEncoding::JsonParsed + { Box::new(iter::once(get_parsed_token_account( bank, ¶ms.pubkey, @@ -381,19 +383,20 @@ fn filter_program_results( RpcFilterType::Memcmp(compare) => compare.bytes_match(account.data()), }) }); - let accounts: Box> = if params.pubkey == spl_token_id() - && params.encoding == UiAccountEncoding::JsonParsed - && !accounts_is_empty - { - Box::new(get_parsed_token_accounts(bank, keyed_accounts)) - } else { - Box::new( - keyed_accounts.map(move |(pubkey, account)| RpcKeyedAccount { - pubkey: pubkey.to_string(), - account: UiAccount::encode(&pubkey, &account, encoding, None, None), - }), - ) - }; + let accounts: Box> = + if is_known_spl_token_id(¶ms.pubkey) + && params.encoding == UiAccountEncoding::JsonParsed + && !accounts_is_empty + { + Box::new(get_parsed_token_accounts(bank, keyed_accounts)) + } else { + Box::new( + keyed_accounts.map(move |(pubkey, account)| RpcKeyedAccount { + pubkey: pubkey.to_string(), + account: UiAccount::encode(&pubkey, &account, encoding, None, None), + }), + ) + }; (accounts, last_notified_slot) } diff --git a/transaction-status/src/parse_instruction.rs b/transaction-status/src/parse_instruction.rs index 563e0f0b54..dc69f7322f 100644 --- a/transaction-status/src/parse_instruction.rs +++ b/transaction-status/src/parse_instruction.rs @@ -10,7 +10,7 @@ use { }, inflector::Inflector, serde_json::Value, - solana_account_decoder::parse_token::spl_token_id, + solana_account_decoder::parse_token::spl_token_ids, solana_sdk::{ instruction::CompiledInstruction, message::AccountKeys, pubkey::Pubkey, stake, system_program, @@ -30,7 +30,6 @@ lazy_static! { static ref MEMO_V3_PROGRAM_ID: Pubkey = spl_memo_id_v3(); static ref STAKE_PROGRAM_ID: Pubkey = stake::program::id(); static ref SYSTEM_PROGRAM_ID: Pubkey = system_program::id(); - static ref TOKEN_PROGRAM_ID: Pubkey = spl_token_id(); static ref VOTE_PROGRAM_ID: Pubkey = solana_vote_program::id(); static ref PARSABLE_PROGRAM_IDS: HashMap = { let mut m = HashMap::new(); @@ -40,7 +39,9 @@ lazy_static! { ); m.insert(*MEMO_V1_PROGRAM_ID, ParsableProgram::SplMemo); m.insert(*MEMO_V3_PROGRAM_ID, ParsableProgram::SplMemo); - m.insert(*TOKEN_PROGRAM_ID, ParsableProgram::SplToken); + for spl_token_id in spl_token_ids() { + m.insert(spl_token_id, ParsableProgram::SplToken); + } m.insert(*BPF_LOADER_PROGRAM_ID, ParsableProgram::BpfLoader); m.insert( *BPF_UPGRADEABLE_LOADER_PROGRAM_ID, diff --git a/transaction-status/src/token_balances.rs b/transaction-status/src/token_balances.rs index 8de5f0e030..fc4f2ad9ed 100644 --- a/transaction-status/src/token_balances.rs +++ b/transaction-status/src/token_balances.rs @@ -1,8 +1,8 @@ use { crate::TransactionTokenBalance, solana_account_decoder::parse_token::{ - pubkey_from_spl_token, spl_token_id, spl_token_native_mint, token_amount_to_ui_amount, - UiTokenAmount, + is_known_spl_token_id, pubkey_from_spl_token, spl_token_native_mint, + token_amount_to_ui_amount, UiTokenAmount, }, solana_measure::measure::Measure, solana_metrics::datapoint_debug, @@ -35,10 +35,6 @@ impl TransactionTokenBalancesSet { } } -fn is_token_program(program_id: &Pubkey) -> bool { - program_id == &spl_token_id() -} - fn get_mint_decimals(bank: &Bank, mint: &Pubkey) -> Option { if mint == &spl_token_native_mint() { Some(spl_token::native_mint::DECIMALS) @@ -63,12 +59,12 @@ pub fn collect_token_balances( for transaction in batch.sanitized_transactions() { let account_keys = transaction.message().account_keys(); - let has_token_program = account_keys.iter().any(is_token_program); + let has_token_program = account_keys.iter().any(is_known_spl_token_id); let mut transaction_balances: Vec = vec![]; if has_token_program { for (index, account_id) in account_keys.iter().enumerate() { - if transaction.message().is_invoked(index) || is_token_program(account_id) { + if transaction.message().is_invoked(index) || is_known_spl_token_id(account_id) { continue; }