Skip to content

Commit

Permalink
Add config param to specify offset/length for single and program acco…
Browse files Browse the repository at this point in the history
…unt info (solana-labs#11515)

* Add config param to specify dataSlice for account info and program accounts

* Use match instead of if
  • Loading branch information
CriesofCarrots authored Aug 10, 2020
1 parent da210dd commit 88ca04d
Show file tree
Hide file tree
Showing 12 changed files with 210 additions and 26 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

68 changes: 63 additions & 5 deletions account-decoder/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,19 +51,23 @@ impl UiAccount {
account: Account,
encoding: UiAccountEncoding,
additional_data: Option<AccountAdditionalData>,
data_slice_config: Option<UiDataSliceConfig>,
) -> Self {
let data = match encoding {
UiAccountEncoding::Binary => {
UiAccountData::Binary(bs58::encode(account.data).into_string())
}
UiAccountEncoding::Binary64 => UiAccountData::Binary64(base64::encode(account.data)),
UiAccountEncoding::Binary => UiAccountData::Binary(
bs58::encode(slice_data(&account.data, data_slice_config)).into_string(),
),
UiAccountEncoding::Binary64 => UiAccountData::Binary64(base64::encode(slice_data(
&account.data,
data_slice_config,
))),
UiAccountEncoding::JsonParsed => {
if let Ok(parsed_data) =
parse_account_data(pubkey, &account.owner, &account.data, additional_data)
{
UiAccountData::Json(parsed_data)
} else {
UiAccountData::Binary64(base64::encode(account.data))
UiAccountData::Binary64(base64::encode(&account.data))
}
}
};
Expand Down Expand Up @@ -113,3 +117,57 @@ impl Default for UiFeeCalculator {
}
}
}

#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UiDataSliceConfig {
pub offset: usize,
pub length: usize,
}

fn slice_data(data: &[u8], data_slice_config: Option<UiDataSliceConfig>) -> &[u8] {
if let Some(UiDataSliceConfig { offset, length }) = data_slice_config {
if offset >= data.len() {
&[]
} else if length > data.len() - offset {
&data[offset..]
} else {
&data[offset..offset + length]
}
} else {
data
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_slice_data() {
let data = vec![1, 2, 3, 4, 5];
let slice_config = Some(UiDataSliceConfig {
offset: 0,
length: 5,
});
assert_eq!(slice_data(&data, slice_config), &data[..]);

let slice_config = Some(UiDataSliceConfig {
offset: 0,
length: 10,
});
assert_eq!(slice_data(&data, slice_config), &data[..]);

let slice_config = Some(UiDataSliceConfig {
offset: 1,
length: 2,
});
assert_eq!(slice_data(&data, slice_config), &data[1..3]);

let slice_config = Some(UiDataSliceConfig {
offset: 10,
length: 2,
});
assert_eq!(slice_data(&data, slice_config), &[] as &[u8]);
}
}
8 changes: 7 additions & 1 deletion cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1120,7 +1120,13 @@ fn process_show_account(
let cli_account = CliAccount {
keyed_account: RpcKeyedAccount {
pubkey: account_pubkey.to_string(),
account: UiAccount::encode(account_pubkey, account, UiAccountEncoding::Binary64, None),
account: UiAccount::encode(
account_pubkey,
account,
UiAccountEncoding::Binary64,
None,
None,
),
},
use_lamports_unit,
};
Expand Down
1 change: 1 addition & 0 deletions cli/src/offline/blockhash_query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,7 @@ mod tests {
nonce_account,
UiAccountEncoding::Binary64,
None,
None,
);
let get_account_response = json!(Response {
context: RpcResponseContext { slot: 1 },
Expand Down
1 change: 1 addition & 0 deletions client/src/rpc_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,7 @@ impl RpcClient {
let config = RpcAccountInfoConfig {
encoding: Some(UiAccountEncoding::Binary64),
commitment: Some(commitment_config),
data_slice: None,
};
let response = self.sender.send(
RpcRequest::GetAccountInfo,
Expand Down
3 changes: 2 additions & 1 deletion client/src/rpc_config.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::rpc_filter::RpcFilterType;
use solana_account_decoder::UiAccountEncoding;
use solana_account_decoder::{UiAccountEncoding, UiDataSliceConfig};
use solana_sdk::{clock::Epoch, commitment_config::CommitmentConfig};

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
Expand Down Expand Up @@ -47,6 +47,7 @@ pub struct RpcStakeConfig {
#[serde(rename_all = "camelCase")]
pub struct RpcAccountInfoConfig {
pub encoding: Option<UiAccountEncoding>,
pub data_slice: Option<UiDataSliceConfig>,
#[serde(flatten)]
pub commitment: Option<CommitmentConfig>,
}
Expand Down
1 change: 1 addition & 0 deletions core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "1.4.0" }
trees = "0.2.1"

[dev-dependencies]
base64 = "0.12.3"
matches = "0.1.6"
reqwest = { version = "0.10.6", default-features = false, features = ["blocking", "rustls-tls", "json"] }
serial_test = "0.4.0"
Expand Down
137 changes: 120 additions & 17 deletions core/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ impl JsonRpcRequestProcessor {
let config = config.unwrap_or_default();
let bank = self.bank(config.commitment);
let encoding = config.encoding.unwrap_or(UiAccountEncoding::Binary);
check_slice_and_encoding(&encoding, config.data_slice.is_some())?;
let mut response = None;
if let Some(account) = bank.get_account(pubkey) {
if account.owner == spl_token_id_v1_0() && encoding == UiAccountEncoding::JsonParsed {
Expand All @@ -256,7 +257,13 @@ impl JsonRpcRequestProcessor {
data: None,
});
} else {
response = Some(UiAccount::encode(pubkey, account, encoding, None));
response = Some(UiAccount::encode(
pubkey,
account,
encoding,
None,
config.data_slice,
));
}
}

Expand All @@ -277,21 +284,31 @@ impl JsonRpcRequestProcessor {
program_id: &Pubkey,
config: Option<RpcAccountInfoConfig>,
filters: Vec<RpcFilterType>,
) -> Vec<RpcKeyedAccount> {
) -> Result<Vec<RpcKeyedAccount>> {
let config = config.unwrap_or_default();
let bank = self.bank(config.commitment);
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 keyed_accounts = get_filtered_program_accounts(&bank, program_id, filters);
if program_id == &spl_token_id_v1_0() && encoding == UiAccountEncoding::JsonParsed {
get_parsed_token_accounts(bank, keyed_accounts).collect()
} else {
keyed_accounts
.map(|(pubkey, account)| RpcKeyedAccount {
pubkey: pubkey.to_string(),
account: UiAccount::encode(&pubkey, account, encoding.clone(), None),
})
.collect()
}
let result =
if program_id == &spl_token_id_v1_0() && encoding == UiAccountEncoding::JsonParsed {
get_parsed_token_accounts(bank, keyed_accounts).collect()
} else {
keyed_accounts
.map(|(pubkey, account)| RpcKeyedAccount {
pubkey: pubkey.to_string(),
account: UiAccount::encode(
&pubkey,
account,
encoding.clone(),
None,
data_slice_config,
),
})
.collect()
};
Ok(result)
}

pub fn get_inflation_governor(
Expand Down Expand Up @@ -1107,6 +1124,8 @@ impl JsonRpcRequestProcessor {
let config = config.unwrap_or_default();
let bank = self.bank(config.commitment);
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 (token_program_id, mint) = get_token_program_id_and_mint(&bank, token_account_filter)?;

let mut filters = vec![
Expand Down Expand Up @@ -1134,7 +1153,13 @@ impl JsonRpcRequestProcessor {
keyed_accounts
.map(|(pubkey, account)| RpcKeyedAccount {
pubkey: pubkey.to_string(),
account: UiAccount::encode(&pubkey, account, encoding.clone(), None),
account: UiAccount::encode(
&pubkey,
account,
encoding.clone(),
None,
data_slice_config,
),
})
.collect()
};
Expand All @@ -1150,6 +1175,8 @@ impl JsonRpcRequestProcessor {
let config = config.unwrap_or_default();
let bank = self.bank(config.commitment);
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 (token_program_id, mint) = get_token_program_id_and_mint(&bank, token_account_filter)?;

let mut filters = vec![
Expand Down Expand Up @@ -1185,7 +1212,13 @@ impl JsonRpcRequestProcessor {
keyed_accounts
.map(|(pubkey, account)| RpcKeyedAccount {
pubkey: pubkey.to_string(),
account: UiAccount::encode(&pubkey, account, encoding.clone(), None),
account: UiAccount::encode(
&pubkey,
account,
encoding.clone(),
None,
data_slice_config,
),
})
.collect()
};
Expand Down Expand Up @@ -1226,6 +1259,26 @@ fn verify_token_account_filter(
}
}

fn check_slice_and_encoding(encoding: &UiAccountEncoding, data_slice_is_some: bool) -> Result<()> {
match encoding {
UiAccountEncoding::JsonParsed => {
if data_slice_is_some {
let message =
"Sliced account data can only be encoded using binary (base 58) or binary64 encoding."
.to_string();
Err(error::Error {
code: error::ErrorCode::InvalidRequest,
message,
data: None,
})
} else {
Ok(())
}
}
UiAccountEncoding::Binary | UiAccountEncoding::Binary64 => Ok(()),
}
}

/// Use a set of filters to get an iterator of keyed program accounts from a bank
fn get_filtered_program_accounts(
bank: &Arc<Bank>,
Expand Down Expand Up @@ -1258,6 +1311,7 @@ pub(crate) fn get_parsed_token_account(
account,
UiAccountEncoding::JsonParsed,
additional_data,
None,
)
}

Expand Down Expand Up @@ -1286,6 +1340,7 @@ where
account,
UiAccountEncoding::JsonParsed,
additional_data,
None,
),
}
})
Expand Down Expand Up @@ -1753,7 +1808,7 @@ impl RpcSol for RpcSolImpl {
for filter in &filters {
verify_filter(filter)?;
}
Ok(meta.get_program_accounts(&program_id, config, filters))
meta.get_program_accounts(&program_id, config, filters)
}

fn get_inflation_governor(
Expand Down Expand Up @@ -3028,13 +3083,13 @@ pub mod tests {
#[test]
fn test_rpc_get_account_info() {
let bob_pubkey = Pubkey::new_rand();
let RpcHandler { io, meta, .. } = start_rpc_handler_with_tx(&bob_pubkey);
let RpcHandler { io, meta, bank, .. } = start_rpc_handler_with_tx(&bob_pubkey);

let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getAccountInfo","params":["{}"]}}"#,
bob_pubkey
);
let res = io.handle_request_sync(&req, meta);
let res = io.handle_request_sync(&req, meta.clone());
let expected = json!({
"jsonrpc": "2.0",
"result": {
Expand All @@ -3054,6 +3109,54 @@ pub mod tests {
let result: Response = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
assert_eq!(expected, result);

let address = Pubkey::new_rand();
let data = vec![1, 2, 3, 4, 5];
let mut account = Account::new(42, 5, &Pubkey::default());
account.data = data.clone();
bank.store_account(&address, &account);

let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getAccountInfo","params":["{}", {{"encoding":"binary64"}}]}}"#,
address
);
let res = io.handle_request_sync(&req, meta.clone());
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
assert_eq!(result["result"]["value"]["data"], base64::encode(&data));

let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getAccountInfo","params":["{}", {{"encoding":"binary64", "dataSlice": {{"length": 2, "offset": 1}}}}]}}"#,
address
);
let res = io.handle_request_sync(&req, meta.clone());
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
assert_eq!(
result["result"]["value"]["data"],
base64::encode(&data[1..3]),
);

let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getAccountInfo","params":["{}", {{"encoding":"binary", "dataSlice": {{"length": 2, "offset": 1}}}}]}}"#,
address
);
let res = io.handle_request_sync(&req, meta.clone());
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
assert_eq!(
result["result"]["value"]["data"],
bs58::encode(&data[1..3]).into_string(),
);

let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getAccountInfo","params":["{}", {{"encoding":"jsonParsed", "dataSlice": {{"length": 2, "offset": 1}}}}]}}"#,
address
);
let res = io.handle_request_sync(&req, meta);
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
result["error"].as_object().unwrap();
}

#[test]
Expand Down
Loading

0 comments on commit 88ca04d

Please sign in to comment.