From 91af4d2fcb9111cf991df17014d3af9e2b0da916 Mon Sep 17 00:00:00 2001 From: Yihau Chen Date: Fri, 19 Jan 2024 11:53:45 +0800 Subject: [PATCH 1/2] rpc: parse token accounts in simulate_transaction (#34619) * rpc: parse token accounts in simulate_transaction * add overwrite_accounts into get_encoded_account and get_parsed_token_account * revert get_mint_decimals scope changes * move common.rs to rpc/account_resolver.rs * rename get_account to get_account_from_overwrites_or_bank * add a comment * clippy * add comment Co-authored-by: Tyera --------- Co-authored-by: Tyera (cherry picked from commit 7470c3d68b5a493a9255c13113cd29335dcb0b09) --- rpc/src/parsed_token_accounts.rs | 15 ++- rpc/src/rpc.rs | 176 ++++++++++++++++++++++++++++--- rpc/src/rpc/account_resolver.rs | 15 +++ rpc/src/rpc_subscriptions.rs | 2 +- 4 files changed, 192 insertions(+), 16 deletions(-) create mode 100644 rpc/src/rpc/account_resolver.rs diff --git a/rpc/src/parsed_token_accounts.rs b/rpc/src/parsed_token_accounts.rs index 597ffb249dee47..d93cda521e65bd 100644 --- a/rpc/src/parsed_token_accounts.rs +++ b/rpc/src/parsed_token_accounts.rs @@ -1,4 +1,5 @@ use { + crate::rpc::account_resolver, jsonrpc_core::{Error, Result}, solana_account_decoder::{ parse_account_data::AccountAdditionalData, parse_token::get_token_account_mint, UiAccount, @@ -18,11 +19,19 @@ pub fn get_parsed_token_account( bank: &Bank, pubkey: &Pubkey, account: AccountSharedData, + // only used for simulation results + overwrite_accounts: Option<&HashMap>, ) -> UiAccount { let additional_data = get_token_account_mint(account.data()) - .and_then(|mint_pubkey| get_mint_owner_and_decimals(bank, &mint_pubkey).ok()) - .map(|(_, decimals)| AccountAdditionalData { - spl_token_decimals: Some(decimals), + .and_then(|mint_pubkey| { + account_resolver::get_account_from_overwrites_or_bank( + &mint_pubkey, + bank, + overwrite_accounts, + ) + }) + .map(|mint_account| AccountAdditionalData { + spl_token_decimals: get_mint_decimals(mint_account.data()).ok(), }); UiAccount::encode( diff --git a/rpc/src/rpc.rs b/rpc/src/rpc.rs index aa1f6ee53f81dc..2bc374041ee297 100644 --- a/rpc/src/rpc.rs +++ b/rpc/src/rpc.rs @@ -112,6 +112,8 @@ use { }, }; +pub mod account_resolver; + type RpcCustomResult = std::result::Result; pub const MAX_REQUEST_BODY_SIZE: usize = 50 * (1 << 10); // 50kB @@ -429,7 +431,7 @@ impl JsonRpcRequestProcessor { })?; let encoding = encoding.unwrap_or(UiAccountEncoding::Binary); - let response = get_encoded_account(&bank, pubkey, encoding, data_slice)?; + let response = get_encoded_account(&bank, pubkey, encoding, data_slice, None)?; Ok(new_response(&bank, response)) } @@ -452,7 +454,7 @@ impl JsonRpcRequestProcessor { let accounts = pubkeys .into_iter() - .map(|pubkey| get_encoded_account(&bank, &pubkey, encoding, data_slice)) + .map(|pubkey| get_encoded_account(&bank, &pubkey, encoding, data_slice, None)) .collect::>>()?; Ok(new_response(&bank, accounts)) } @@ -2285,13 +2287,15 @@ fn get_encoded_account( pubkey: &Pubkey, encoding: UiAccountEncoding, data_slice: Option, + // only used for simulation results + overwrite_accounts: Option<&HashMap>, ) -> Result> { - match bank.get_account(pubkey) { + match account_resolver::get_account_from_overwrites_or_bank(pubkey, bank, overwrite_accounts) { Some(account) => { let response = if is_known_spl_token_id(account.owner()) && encoding == UiAccountEncoding::JsonParsed { - get_parsed_token_account(bank, pubkey, account) + get_parsed_token_account(bank, pubkey, account, overwrite_accounts) } else { encode_account(&account, pubkey, encoding, data_slice)? }; @@ -3783,19 +3787,24 @@ pub mod rpc_full { if result.is_err() { Some(vec![None; config_accounts.addresses.len()]) } else { + let mut post_simulation_accounts_map = HashMap::new(); + for (pubkey, data) in post_simulation_accounts { + post_simulation_accounts_map.insert(pubkey, data); + } + Some( config_accounts .addresses .iter() .map(|address_str| { - let address = verify_pubkey(address_str)?; - post_simulation_accounts - .iter() - .find(|(key, _account)| key == &address) - .map(|(pubkey, account)| { - encode_account(account, pubkey, accounts_encoding, None) - }) - .transpose() + let pubkey = verify_pubkey(address_str)?; + get_encoded_account( + bank, + &pubkey, + accounts_encoding, + None, + Some(&post_simulation_accounts_map), + ) }) .collect::>>()?, ) @@ -6112,6 +6121,149 @@ pub mod tests { assert_eq!(result, expected); } + #[test] + fn test_rpc_simulate_transaction_with_parsing_token_accounts() { + let rpc = RpcHandler::start(); + let bank = rpc.working_bank(); + let RpcHandler { + ref meta, ref io, .. + } = rpc; + + // init mint + let mint_rent_exempt_amount = + bank.get_minimum_balance_for_rent_exemption(spl_token::state::Mint::LEN); + let mint_pubkey = Pubkey::from_str("mint111111111111111111111111111111111111111").unwrap(); + let mut mint_data = [0u8; spl_token::state::Mint::LEN]; + Pack::pack_into_slice( + &spl_token::state::Mint { + mint_authority: COption::None, + supply: 0, + decimals: 8, + is_initialized: true, + freeze_authority: COption::None, + }, + &mut mint_data, + ); + let account = AccountSharedData::create( + mint_rent_exempt_amount, + mint_data.into(), + spl_token::id(), + false, + 0, + ); + bank.store_account(&mint_pubkey, &account); + + // init token account + let token_account_rent_exempt_amount = + bank.get_minimum_balance_for_rent_exemption(spl_token::state::Account::LEN); + let token_account_pubkey = Pubkey::new_unique(); + let owner_pubkey = Pubkey::from_str("owner11111111111111111111111111111111111111").unwrap(); + let mut token_account_data = [0u8; spl_token::state::Account::LEN]; + Pack::pack_into_slice( + &spl_token::state::Account { + mint: mint_pubkey, + owner: owner_pubkey, + amount: 1, + delegate: COption::None, + state: spl_token::state::AccountState::Initialized, + is_native: COption::None, + delegated_amount: 0, + close_authority: COption::None, + }, + &mut token_account_data, + ); + let account = AccountSharedData::create( + token_account_rent_exempt_amount, + token_account_data.into(), + spl_token::id(), + false, + 0, + ); + bank.store_account(&token_account_pubkey, &account); + + // prepare tx + let fee_payer = rpc.mint_keypair; + let recent_blockhash = bank.confirmed_last_blockhash(); + let tx = + system_transaction::transfer(&fee_payer, &token_account_pubkey, 1, recent_blockhash); + let tx_serialized_encoded = bs58::encode(serialize(&tx).unwrap()).into_string(); + + // Simulation bank must be frozen + bank.freeze(); + + let req = format!( + r#"{{"jsonrpc":"2.0", + "id":1, + "method":"simulateTransaction", + "params":[ + "{}", + {{ + "sigVerify": true, + "accounts": {{ + "encoding": "jsonParsed", + "addresses": ["{}", "{}"] + }} + }} + ] + }}"#, + tx_serialized_encoded, + solana_sdk::pubkey::new_rand(), + token_account_pubkey, + ); + let res = io.handle_request_sync(&req, meta.clone()); + let expected = json!({ + "jsonrpc": "2.0", + "result": { + "context": {"slot": 0, "apiVersion": RpcApiVersion::default()}, + "value":{ + "accounts": [ + null, + { + "data": { + "parsed": { + "info": { + "isNative": false, + "mint": "mint111111111111111111111111111111111111111", + "owner": "owner11111111111111111111111111111111111111", + "state": "initialized", + "tokenAmount": { + "amount": "1", + "decimals": 8, + "uiAmount": 0.00000001, + "uiAmountString": "0.00000001" + } + }, + "type": "account" + }, + "program": "spl-token", + "space": 165 + }, + "executable": false, + "lamports": (token_account_rent_exempt_amount + 1), + "owner": bs58::encode(spl_token::id()).into_string(), + "rentEpoch": u64::MAX, + "space": spl_token::state::Account::LEN + }, + ], + "err": null, + "innerInstructions": null, + "logs":[ + "Program 11111111111111111111111111111111 invoke [1]", + "Program 11111111111111111111111111111111 success" + ], + "returnData": null, + "unitsConsumed": 150, + } + }, + "id": 1, + }); + let expected: Response = + serde_json::from_value(expected).expect("expected response deserialization"); + let result: Response = serde_json::from_str(&res.expect("actual response")) + .expect("actual response deserialization"); + assert_eq!(result, expected); + } + #[test] #[should_panic(expected = "simulation bank must be frozen")] fn test_rpc_simulate_transaction_panic_on_unfrozen_bank() { diff --git a/rpc/src/rpc/account_resolver.rs b/rpc/src/rpc/account_resolver.rs new file mode 100644 index 00000000000000..44d232a24b1bc2 --- /dev/null +++ b/rpc/src/rpc/account_resolver.rs @@ -0,0 +1,15 @@ +use { + solana_runtime::bank::Bank, + solana_sdk::{account::AccountSharedData, pubkey::Pubkey}, + std::collections::HashMap, +}; + +pub(crate) fn get_account_from_overwrites_or_bank( + pubkey: &Pubkey, + bank: &Bank, + overwrite_accounts: Option<&HashMap>, +) -> Option { + overwrite_accounts + .and_then(|accounts| accounts.get(pubkey).cloned()) + .or_else(|| bank.get_account(pubkey)) +} diff --git a/rpc/src/rpc_subscriptions.rs b/rpc/src/rpc_subscriptions.rs index 04f551bfe0b9ff..7910e539fad46f 100644 --- a/rpc/src/rpc_subscriptions.rs +++ b/rpc/src/rpc_subscriptions.rs @@ -383,7 +383,7 @@ fn filter_account_result( if is_known_spl_token_id(account.owner()) && params.encoding == UiAccountEncoding::JsonParsed { - get_parsed_token_account(&bank, ¶ms.pubkey, account) + get_parsed_token_account(&bank, ¶ms.pubkey, account, None) } else { UiAccount::encode(¶ms.pubkey, &account, params.encoding, None, None) } From cfa480e151564233451c01b370719908991cdf64 Mon Sep 17 00:00:00 2001 From: yihau Date: Fri, 19 Jan 2024 18:29:49 +0800 Subject: [PATCH 2/2] remove innerInstructions --- rpc/src/rpc.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/rpc/src/rpc.rs b/rpc/src/rpc.rs index 2bc374041ee297..d7d0d8b6cc172d 100644 --- a/rpc/src/rpc.rs +++ b/rpc/src/rpc.rs @@ -6246,7 +6246,6 @@ pub mod tests { }, ], "err": null, - "innerInstructions": null, "logs":[ "Program 11111111111111111111111111111111 invoke [1]", "Program 11111111111111111111111111111111 success"