Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v1.17: rpc: parse token accounts in simulate_transaction (backport of #34619) #34852

Merged
merged 2 commits into from
Jan 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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