rpc: add context toggle to getProgramAccounts (#17399)

* fix(rpc): return context in get_program_accounts

* doc(rpc): document withContext flag

* fix(rpc): fix comment

Co-authored-by: Michael Vines <mvines@gmail.com>

* fix(rpc): fix doc

Co-authored-by: Michael Vines <mvines@gmail.com>

Co-authored-by: Michael Vines <mvines@gmail.com>
This commit is contained in:
Nikita
2021-05-22 10:12:21 +03:00
committed by GitHub
parent 51178ccb33
commit d41266e4e9
6 changed files with 53 additions and 12 deletions

View File

@ -1676,11 +1676,11 @@ pub fn process_show_stakes(
progress_bar.set_message("Fetching stake accounts..."); progress_bar.set_message("Fetching stake accounts...");
let mut program_accounts_config = RpcProgramAccountsConfig { let mut program_accounts_config = RpcProgramAccountsConfig {
filters: None,
account_config: RpcAccountInfoConfig { account_config: RpcAccountInfoConfig {
encoding: Some(solana_account_decoder::UiAccountEncoding::Base64), encoding: Some(solana_account_decoder::UiAccountEncoding::Base64),
..RpcAccountInfoConfig::default() ..RpcAccountInfoConfig::default()
}, },
..RpcProgramAccountsConfig::default()
}; };
if let Some(vote_account_pubkeys) = vote_account_pubkeys { if let Some(vote_account_pubkeys) = vote_account_pubkeys {

View File

@ -1102,6 +1102,7 @@ fn get_buffers(
data_slice: Some(UiDataSliceConfig { offset: 0, length }), data_slice: Some(UiDataSliceConfig { offset: 0, length }),
..RpcAccountInfoConfig::default() ..RpcAccountInfoConfig::default()
}, },
..RpcProgramAccountsConfig::default()
}, },
)?; )?;
Ok(results) Ok(results)
@ -1406,6 +1407,7 @@ fn process_close(
data_slice: Some(UiDataSliceConfig { offset: 0, length }), data_slice: Some(UiDataSliceConfig { offset: 0, length }),
..RpcAccountInfoConfig::default() ..RpcAccountInfoConfig::default()
}, },
..RpcProgramAccountsConfig::default()
}, },
)?; )?;

View File

@ -1163,11 +1163,11 @@ impl RpcClient {
self.get_program_accounts_with_config( self.get_program_accounts_with_config(
pubkey, pubkey,
RpcProgramAccountsConfig { RpcProgramAccountsConfig {
filters: None,
account_config: RpcAccountInfoConfig { account_config: RpcAccountInfoConfig {
encoding: Some(UiAccountEncoding::Base64Zstd), encoding: Some(UiAccountEncoding::Base64Zstd),
..RpcAccountInfoConfig::default() ..RpcAccountInfoConfig::default()
}, },
..RpcProgramAccountsConfig::default()
}, },
) )
} }

View File

@ -127,6 +127,7 @@ pub struct RpcProgramAccountsConfig {
pub filters: Option<Vec<RpcFilterType>>, pub filters: Option<Vec<RpcFilterType>>,
#[serde(flatten)] #[serde(flatten)]
pub account_config: RpcAccountInfoConfig, pub account_config: RpcAccountInfoConfig,
pub with_context: Option<bool>,
} }
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]

View File

@ -10,6 +10,7 @@ use crate::{
use bincode::{config::Options, serialize}; use bincode::{config::Options, serialize};
use jsonrpc_core::{types::error, Error, Metadata, Result}; use jsonrpc_core::{types::error, Error, Metadata, Result};
use jsonrpc_derive::rpc; use jsonrpc_derive::rpc;
use serde::{Deserialize, Serialize};
use solana_account_decoder::{ use solana_account_decoder::{
parse_token::{spl_token_id_v2_0, token_amount_to_ui_amount, UiTokenAmount}, parse_token::{spl_token_id_v2_0, token_amount_to_ui_amount, UiTokenAmount},
UiAccount, UiAccountEncoding, UiDataSliceConfig, UiAccount, UiAccountEncoding, UiDataSliceConfig,
@ -103,6 +104,16 @@ fn new_response<T>(bank: &Bank, value: T) -> RpcResponse<T> {
Response { context, value } Response { context, value }
} }
/// Wrapper for rpc return types of methods that provide responses both with and without context.
/// Main purpose of this is to fix methods that lack context information in their return type,
/// without breaking backwards compatibility.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum OptionalContext<T> {
Context(RpcResponse<T>),
NoContext(T),
}
fn is_finalized( fn is_finalized(
block_commitment_cache: &BlockCommitmentCache, block_commitment_cache: &BlockCommitmentCache,
bank: &Bank, bank: &Bank,
@ -350,7 +361,8 @@ impl JsonRpcRequestProcessor {
program_id: &Pubkey, program_id: &Pubkey,
config: Option<RpcAccountInfoConfig>, config: Option<RpcAccountInfoConfig>,
filters: Vec<RpcFilterType>, filters: Vec<RpcFilterType>,
) -> Result<Vec<RpcKeyedAccount>> { with_context: bool,
) -> Result<OptionalContext<Vec<RpcKeyedAccount>>> {
let config = config.unwrap_or_default(); let config = config.unwrap_or_default();
let bank = self.bank(config.commitment); let bank = self.bank(config.commitment);
let encoding = config.encoding.unwrap_or(UiAccountEncoding::Binary); let encoding = config.encoding.unwrap_or(UiAccountEncoding::Binary);
@ -367,7 +379,7 @@ impl JsonRpcRequestProcessor {
}; };
let result = let result =
if program_id == &spl_token_id_v2_0() && encoding == UiAccountEncoding::JsonParsed { if program_id == &spl_token_id_v2_0() && encoding == UiAccountEncoding::JsonParsed {
get_parsed_token_accounts(bank, keyed_accounts.into_iter()).collect() get_parsed_token_accounts(bank.clone(), keyed_accounts.into_iter()).collect()
} else { } else {
keyed_accounts keyed_accounts
.into_iter() .into_iter()
@ -383,7 +395,10 @@ impl JsonRpcRequestProcessor {
}) })
.collect() .collect()
}; };
Ok(result) Ok(result).map(|result| match with_context {
true => OptionalContext::Context(new_response(&bank, result)),
false => OptionalContext::NoContext(result),
})
} }
pub fn get_inflation_reward( pub fn get_inflation_reward(
@ -2274,7 +2289,7 @@ pub mod rpc_full {
meta: Self::Metadata, meta: Self::Metadata,
program_id_str: String, program_id_str: String,
config: Option<RpcProgramAccountsConfig>, config: Option<RpcProgramAccountsConfig>,
) -> Result<Vec<RpcKeyedAccount>>; ) -> Result<OptionalContext<Vec<RpcKeyedAccount>>>;
#[rpc(meta, name = "getMinimumBalanceForRentExemption")] #[rpc(meta, name = "getMinimumBalanceForRentExemption")]
fn get_minimum_balance_for_rent_exemption( fn get_minimum_balance_for_rent_exemption(
@ -2599,19 +2614,20 @@ pub mod rpc_full {
meta: Self::Metadata, meta: Self::Metadata,
program_id_str: String, program_id_str: String,
config: Option<RpcProgramAccountsConfig>, config: Option<RpcProgramAccountsConfig>,
) -> Result<Vec<RpcKeyedAccount>> { ) -> Result<OptionalContext<Vec<RpcKeyedAccount>>> {
debug!( debug!(
"get_program_accounts rpc request received: {:?}", "get_program_accounts rpc request received: {:?}",
program_id_str program_id_str
); );
let program_id = verify_pubkey(&program_id_str)?; let program_id = verify_pubkey(&program_id_str)?;
let (config, filters) = if let Some(config) = config { let (config, filters, with_context) = if let Some(config) = config {
( (
Some(config.account_config), Some(config.account_config),
config.filters.unwrap_or_default(), config.filters.unwrap_or_default(),
config.with_context.unwrap_or_default(),
) )
} else { } else {
(None, vec![]) (None, vec![], false)
}; };
if filters.len() > MAX_GET_PROGRAM_ACCOUNT_FILTERS { if filters.len() > MAX_GET_PROGRAM_ACCOUNT_FILTERS {
return Err(Error::invalid_params(format!( return Err(Error::invalid_params(format!(
@ -2622,7 +2638,7 @@ pub mod rpc_full {
for filter in &filters { for filter in &filters {
verify_filter(filter)?; verify_filter(filter)?;
} }
meta.get_program_accounts(&program_id, config, filters) meta.get_program_accounts(&program_id, config, filters, with_context)
} }
fn get_inflation_governor( fn get_inflation_governor(
@ -4697,6 +4713,26 @@ pub mod tests {
.expect("actual response deserialization"); .expect("actual response deserialization");
assert_eq!(expected, result); assert_eq!(expected, result);
// Test returns context
let req = format!(
r#"{{
"jsonrpc":"2.0",
"id":1,
"method":"getProgramAccounts",
"params":["{}",{{
"withContext": true
}}]
}}"#,
system_program::id(),
);
let res = io.handle_request_sync(&req, meta.clone());
let result: Value = serde_json::from_str(&res.expect("actual response")).unwrap();
let contains_slot = result["result"]["context"]
.as_object()
.expect("must contain context")
.contains_key("slot");
assert!(contains_slot);
// Set up nonce accounts to test filters // Set up nonce accounts to test filters
let nonce_keypair0 = Keypair::new(); let nonce_keypair0 = Keypair::new();
let instruction = system_instruction::create_nonce_account( let instruction = system_instruction::create_nonce_account(

View File

@ -1780,7 +1780,7 @@ Returns all accounts owned by the provided program Pubkey
"jsonParsed" encoding attempts to use program-specific state parsers to return more human-readable and explicit account state data. If "jsonParsed" is requested but a parser cannot be found, the field falls back to "base64" encoding, detectable when the `data` field is type `<string>`. "jsonParsed" encoding attempts to use program-specific state parsers to return more human-readable and explicit account state data. If "jsonParsed" is requested but a parser cannot be found, the field falls back to "base64" encoding, detectable when the `data` field is type `<string>`.
- (optional) `dataSlice: <object>` - limit the returned account data using the provided `offset: <usize>` and `length: <usize>` fields; only available for "base58", "base64" or "base64+zstd" encodings. - (optional) `dataSlice: <object>` - limit the returned account data using the provided `offset: <usize>` and `length: <usize>` fields; only available for "base58", "base64" or "base64+zstd" encodings.
- (optional) `filters: <array>` - filter results using various [filter objects](jsonrpc-api.md#filters); account must meet all filter criteria to be included in results - (optional) `filters: <array>` - filter results using various [filter objects](jsonrpc-api.md#filters); account must meet all filter criteria to be included in results
- (optional) `withContext: bool` - wrap the result in an RpcResponse JSON object.
##### Filters: ##### Filters:
- `memcmp: <object>` - compares a provided series of bytes with program account data at a particular offset. Fields: - `memcmp: <object>` - compares a provided series of bytes with program account data at a particular offset. Fields:
- `offset: <usize>` - offset into program account data to start comparison - `offset: <usize>` - offset into program account data to start comparison
@ -1790,7 +1790,9 @@ Returns all accounts owned by the provided program Pubkey
#### Results: #### Results:
The result field will be an array of JSON objects, which will contain: By default the result field will be an array of JSON objects. If `withContext` flag is set the array will be wrapped in an RpcResponse JSON object.
The array will contain:
- `pubkey: <string>` - the account Pubkey as base-58 encoded string - `pubkey: <string>` - the account Pubkey as base-58 encoded string
- `account: <object>` - a JSON object, with the following sub fields: - `account: <object>` - a JSON object, with the following sub fields: