Skip to content

Commit

Permalink
v1.17: rpc: parse token accounts in simulate_transaction (backport of #…
Browse files Browse the repository at this point in the history
…34619) (#34852)

* 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 <[email protected]>

---------

Co-authored-by: Tyera <[email protected]>
(cherry picked from commit 7470c3d)

* remove innerInstructions

---------

Co-authored-by: Yihau Chen <[email protected]>
Co-authored-by: yihau <[email protected]>
  • Loading branch information
3 people authored Jan 20, 2024
1 parent 407dfba commit d878262
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 16 deletions.
15 changes: 12 additions & 3 deletions rpc/src/parsed_token_accounts.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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<Pubkey, AccountSharedData>>,
) -> 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(
Expand Down
175 changes: 163 additions & 12 deletions rpc/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ use {
},
};

pub mod account_resolver;

type RpcCustomResult<T> = std::result::Result<T, RpcCustomError>;

pub const MAX_REQUEST_BODY_SIZE: usize = 50 * (1 << 10); // 50kB
Expand Down Expand Up @@ -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))
}

Expand All @@ -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::<Result<Vec<_>>>()?;
Ok(new_response(&bank, accounts))
}
Expand Down Expand Up @@ -2285,13 +2287,15 @@ fn get_encoded_account(
pubkey: &Pubkey,
encoding: UiAccountEncoding,
data_slice: Option<UiDataSliceConfig>,
// only used for simulation results
overwrite_accounts: Option<&HashMap<Pubkey, AccountSharedData>>,
) -> Result<Option<UiAccount>> {
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)?
};
Expand Down Expand Up @@ -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::<Result<Vec<_>>>()?,
)
Expand Down Expand Up @@ -6112,6 +6121,148 @@ 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,
"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() {
Expand Down
15 changes: 15 additions & 0 deletions rpc/src/rpc/account_resolver.rs
Original file line number Diff line number Diff line change
@@ -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<Pubkey, AccountSharedData>>,
) -> Option<AccountSharedData> {
overwrite_accounts
.and_then(|accounts| accounts.get(pubkey).cloned())
.or_else(|| bank.get_account(pubkey))
}
2 changes: 1 addition & 1 deletion rpc/src/rpc_subscriptions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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, &params.pubkey, account)
get_parsed_token_account(&bank, &params.pubkey, account, None)
} else {
UiAccount::encode(&params.pubkey, &account, params.encoding, None, None)
}
Expand Down

0 comments on commit d878262

Please sign in to comment.