From 3d5ff7968e8697796976a15b67681256a91ddfac Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 18 Mar 2021 19:33:01 +0000 Subject: [PATCH] rpc: Add config options limiting getConfirmedBlock response data (#15970) (#15995) * Add new confirmed block struct * Add RpcConfirmedBlockConfig options * Configure block response based on new options * Add client api, use in cli fetch_epoch_rewards * Update docs * Apply review suggestions (cherry picked from commit aa54c468eaf0cbb152f56290d50cf9ddd26d25f2) Co-authored-by: Tyera Eulberg --- cli/src/stake.rs | 7 +- client/src/rpc_client.rs | 17 ++++- client/src/rpc_config.rs | 14 +++- core/src/rpc.rs | 78 +++++++++++++++++--- docs/src/developing/clients/jsonrpc-api.md | 10 ++- transaction-status/src/lib.rs | 85 ++++++++++++++++++++++ 6 files changed, 187 insertions(+), 24 deletions(-) diff --git a/cli/src/stake.rs b/cli/src/stake.rs index de71f3c87f58b2..0e92aea913f00a 100644 --- a/cli/src/stake.rs +++ b/cli/src/stake.rs @@ -27,6 +27,7 @@ use solana_client::{ client_error::{ClientError, ClientErrorKind}, nonce_utils, rpc_client::RpcClient, + rpc_config::RpcConfirmedBlockConfig, rpc_custom_error, rpc_request::{self, DELINQUENT_VALIDATOR_SLOT_DISTANCE}, }; @@ -1674,9 +1675,9 @@ pub(crate) fn fetch_epoch_rewards( .get(0) .ok_or_else(|| format!("Unable to fetch first confirmed block for epoch {}", epoch))?; - let first_confirmed_block = match rpc_client.get_confirmed_block_with_encoding( + let first_confirmed_block = match rpc_client.get_configured_confirmed_block( first_confirmed_block_in_epoch, - solana_transaction_status::UiTransactionEncoding::Base64, + RpcConfirmedBlockConfig::rewards_only(), ) { Ok(first_confirmed_block) => first_confirmed_block, Err(ClientError { @@ -1702,7 +1703,7 @@ pub(crate) fn fetch_epoch_rewards( }; // Rewards for the previous epoch are found in the first confirmed block of the current epoch - let previous_epoch_rewards = first_confirmed_block.rewards; + let previous_epoch_rewards = first_confirmed_block.rewards.unwrap_or_default(); if let Some((effective_slot, epoch_end_time, epoch_rewards)) = epoch_info { let wallclock_epoch_duration = if epoch_end_time > epoch_start_time { diff --git a/client/src/rpc_client.rs b/client/src/rpc_client.rs index 2ba50e51c7b0e2..5a57de4bbc6013 100644 --- a/client/src/rpc_client.rs +++ b/client/src/rpc_client.rs @@ -4,9 +4,9 @@ use crate::{ mock_sender::{MockSender, Mocks}, rpc_config::RpcAccountInfoConfig, rpc_config::{ - RpcGetConfirmedSignaturesForAddress2Config, RpcLargestAccountsConfig, - RpcProgramAccountsConfig, RpcSendTransactionConfig, RpcSimulateTransactionConfig, - RpcTokenAccountsFilter, + RpcConfirmedBlockConfig, RpcGetConfirmedSignaturesForAddress2Config, + RpcLargestAccountsConfig, RpcProgramAccountsConfig, RpcSendTransactionConfig, + RpcSimulateTransactionConfig, RpcTokenAccountsFilter, }, rpc_request::{RpcError, RpcRequest, RpcResponseErrorData, TokenAccountsFilter}, rpc_response::*, @@ -33,7 +33,8 @@ use solana_sdk::{ transaction::{self, uses_durable_nonce, Transaction}, }; use solana_transaction_status::{ - EncodedConfirmedBlock, EncodedConfirmedTransaction, TransactionStatus, UiTransactionEncoding, + EncodedConfirmedBlock, EncodedConfirmedTransaction, TransactionStatus, UiConfirmedBlock, + UiTransactionEncoding, }; use solana_vote_program::vote_state::MAX_LOCKOUT_HISTORY; use std::{ @@ -507,6 +508,14 @@ impl RpcClient { self.send(RpcRequest::GetConfirmedBlock, json!([slot, encoding])) } + pub fn get_configured_confirmed_block( + &self, + slot: Slot, + config: RpcConfirmedBlockConfig, + ) -> ClientResult { + self.send(RpcRequest::GetConfirmedBlock, json!([slot, config])) + } + pub fn get_confirmed_blocks( &self, start_slot: Slot, diff --git a/client/src/rpc_config.rs b/client/src/rpc_config.rs index cb7b913093afa5..f2af1beaa272e0 100644 --- a/client/src/rpc_config.rs +++ b/client/src/rpc_config.rs @@ -4,7 +4,7 @@ use solana_sdk::{ clock::Epoch, commitment_config::{CommitmentConfig, CommitmentLevel}, }; -use solana_transaction_status::UiTransactionEncoding; +use solana_transaction_status::{TransactionDetails, UiTransactionEncoding}; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -133,12 +133,24 @@ pub trait EncodingConfig { #[serde(rename_all = "camelCase")] pub struct RpcConfirmedBlockConfig { pub encoding: Option, + pub transaction_details: Option, + pub rewards: Option, } impl EncodingConfig for RpcConfirmedBlockConfig { fn new_with_encoding(encoding: &Option) -> Self { Self { encoding: *encoding, + ..Self::default() + } + } +} + +impl RpcConfirmedBlockConfig { + pub fn rewards_only() -> Self { + Self { + transaction_details: Some(TransactionDetails::None), + ..Self::default() } } } diff --git a/core/src/rpc.rs b/core/src/rpc.rs index 3470aa38c7c95b..fcb476d1928931 100644 --- a/core/src/rpc.rs +++ b/core/src/rpc.rs @@ -66,8 +66,8 @@ use solana_sdk::{ }; use solana_stake_program::stake_state::StakeState; use solana_transaction_status::{ - EncodedConfirmedBlock, EncodedConfirmedTransaction, TransactionConfirmationStatus, - TransactionStatus, UiTransactionEncoding, + EncodedConfirmedTransaction, TransactionConfirmationStatus, TransactionStatus, + UiConfirmedBlock, UiTransactionEncoding, }; use solana_vote_program::vote_state::{VoteState, MAX_LOCKOUT_HISTORY}; use spl_token_v2_0::{ @@ -720,11 +720,13 @@ impl JsonRpcRequestProcessor { &self, slot: Slot, config: Option>, - ) -> Result> { + ) -> Result> { let config = config .map(|config| config.convert_to_current()) .unwrap_or_default(); let encoding = config.encoding.unwrap_or(UiTransactionEncoding::Json); + let transaction_details = config.transaction_details.unwrap_or_default(); + let show_rewards = config.rewards.unwrap_or(true); if self.config.enable_rpc_transaction_history && slot <= self @@ -741,15 +743,15 @@ impl JsonRpcRequestProcessor { .runtime .block_on(bigtable_ledger_storage.get_confirmed_block(slot)); self.check_bigtable_result(&bigtable_result)?; - return Ok(bigtable_result - .ok() - .map(|confirmed_block| confirmed_block.encode(encoding))); + return Ok(bigtable_result.ok().map(|confirmed_block| { + confirmed_block.configure(encoding, transaction_details, show_rewards) + })); } } self.check_slot_cleaned_up(&result, slot)?; - Ok(result - .ok() - .map(|confirmed_block| confirmed_block.encode(encoding))) + Ok(result.ok().map(|confirmed_block| { + confirmed_block.configure(encoding, transaction_details, show_rewards) + })) } else { Err(RpcCustomError::BlockNotAvailable { slot }.into()) } @@ -2185,7 +2187,7 @@ pub mod rpc_full { meta: Self::Metadata, slot: Slot, config: Option>, - ) -> Result>; + ) -> Result>; #[rpc(meta, name = "getBlockTime")] fn get_block_time(&self, meta: Self::Metadata, slot: Slot) @@ -2793,7 +2795,7 @@ pub mod rpc_full { meta: Self::Metadata, slot: Slot, config: Option>, - ) -> Result> { + ) -> Result> { debug!("get_confirmed_block rpc request received: {:?}", slot); meta.get_confirmed_block(slot, config) } @@ -3119,7 +3121,8 @@ pub mod tests { transaction::{self, TransactionError}, }; use solana_transaction_status::{ - EncodedTransaction, EncodedTransactionWithStatusMeta, UiMessage, + EncodedConfirmedBlock, EncodedTransaction, EncodedTransactionWithStatusMeta, + TransactionDetails, UiMessage, }; use solana_vote_program::{ vote_instruction, @@ -5077,6 +5080,7 @@ pub mod tests { serde_json::from_value(result["result"].clone()).unwrap(); let confirmed_block = confirmed_block.unwrap(); assert_eq!(confirmed_block.transactions.len(), 3); + assert_eq!(confirmed_block.rewards, vec![]); for EncodedTransactionWithStatusMeta { transaction, meta } in confirmed_block.transactions.into_iter() @@ -5121,6 +5125,7 @@ pub mod tests { serde_json::from_value(result["result"].clone()).unwrap(); let confirmed_block = confirmed_block.unwrap(); assert_eq!(confirmed_block.transactions.len(), 3); + assert_eq!(confirmed_block.rewards, vec![]); for EncodedTransactionWithStatusMeta { transaction, meta } in confirmed_block.transactions.into_iter() @@ -5156,6 +5161,55 @@ pub mod tests { } } + #[test] + fn test_get_confirmed_block_config() { + let bob_pubkey = solana_sdk::pubkey::new_rand(); + let RpcHandler { + io, + meta, + confirmed_block_signatures, + .. + } = start_rpc_handler_with_tx(&bob_pubkey); + + let req = format!( + r#"{{"jsonrpc":"2.0","id":1,"method":"getConfirmedBlock","params":[0,{}]}}"#, + json!(RpcConfirmedBlockConfig { + encoding: None, + transaction_details: Some(TransactionDetails::Signatures), + rewards: Some(false), + }) + ); + let res = io.handle_request_sync(&req, meta.clone()); + let result: Value = serde_json::from_str(&res.expect("actual response")) + .expect("actual response deserialization"); + let confirmed_block: Option = + serde_json::from_value(result["result"].clone()).unwrap(); + let confirmed_block = confirmed_block.unwrap(); + assert!(confirmed_block.transactions.is_none()); + assert!(confirmed_block.rewards.is_none()); + for (i, signature) in confirmed_block.signatures.unwrap()[..2].iter().enumerate() { + assert_eq!(*signature, confirmed_block_signatures[i].to_string()); + } + + let req = format!( + r#"{{"jsonrpc":"2.0","id":1,"method":"getConfirmedBlock","params":[0,{}]}}"#, + json!(RpcConfirmedBlockConfig { + encoding: None, + transaction_details: Some(TransactionDetails::None), + rewards: Some(true), + }) + ); + let res = io.handle_request_sync(&req, meta); + let result: Value = serde_json::from_str(&res.expect("actual response")) + .expect("actual response deserialization"); + let confirmed_block: Option = + serde_json::from_value(result["result"].clone()).unwrap(); + let confirmed_block = confirmed_block.unwrap(); + assert!(confirmed_block.transactions.is_none()); + assert!(confirmed_block.signatures.is_none()); + assert_eq!(confirmed_block.rewards.unwrap(), vec![]); + } + #[test] fn test_get_confirmed_blocks() { let bob_pubkey = solana_sdk::pubkey::new_rand(); diff --git a/docs/src/developing/clients/jsonrpc-api.md b/docs/src/developing/clients/jsonrpc-api.md index 80ee4a86ed6a75..5ba69ed9cf2d06 100644 --- a/docs/src/developing/clients/jsonrpc-api.md +++ b/docs/src/developing/clients/jsonrpc-api.md @@ -460,6 +460,8 @@ Returns identity and transaction information about a confirmed block in the ledg - `` - (optional) Configuration object containing the following optional fields: - (optional) `encoding: ` - encoding for each returned Transaction, either "json", "jsonParsed", "base58" (*slow*), "base64". If parameter not provided, the default encoding is "json". "jsonParsed" encoding attempts to use program-specific instruction parsers to return more human-readable and explicit data in the `transaction.message.instructions` list. If "jsonParsed" is requested but a parser cannot be found, the instruction falls back to regular JSON encoding (`accounts`, `data`, and `programIdIndex` fields). + - (optional) `transactionDetails: ` - level of transaction detail to return, either "full", "signatures", or "none". If parameter not provided, the default detail level is "full". + - (optional) `rewards: bool` - whether to populate the `rewards` array. If parameter not provided, the default includes rewards. #### Results: @@ -470,7 +472,7 @@ The result field will be an object with the following fields: - `blockhash: ` - the blockhash of this block, as base-58 encoded string - `previousBlockhash: ` - the blockhash of this block's parent, as base-58 encoded string; if the parent block is not available due to ledger cleanup, this field will return "11111111111111111111111111111111" - `parentSlot: ` - the slot index of this block's parent - - `transactions: ` - an array of JSON objects containing: + - `transactions: ` - present if "full" transaction details are requested; an array of JSON objects containing: - `transaction: ` - [Transaction](#transaction-structure) object, either in JSON format or encoded binary data, depending on encoding parameter - `meta: ` - transaction status metadata object, containing `null` or: - `err: ` - Error if transaction failed, null if transaction succeeded. [TransactionError definitions](https://github.com/solana-labs/solana/blob/master/sdk/src/transaction.rs#L24) @@ -484,7 +486,8 @@ The result field will be an object with the following fields: - DEPRECATED: `status: ` - Transaction status - `"Ok": ` - Transaction was successful - `"Err": ` - Transaction failed with TransactionError - - `rewards: ` - an array of JSON objects containing: + - `signatures: ` - present if "signatures" are requested for transaction details; an array of signatures strings, corresponding to the transaction order in the block + - `rewards: ` - present if rewards are requested; an array of JSON objects containing: - `pubkey: ` - The public key, as base-58 encoded string, of the account that received the reward - `lamports: `- number of reward lamports credited or debited by the account, as a i64 - `postBalance: ` - account balance in lamports after the reward was applied @@ -496,7 +499,7 @@ The result field will be an object with the following fields: Request: ```bash curl http://localhost:8899 -X POST -H "Content-Type: application/json" -d ' - {"jsonrpc": "2.0","id":1,"method":"getConfirmedBlock","params":[430, {"encoding": "json"}]} + {"jsonrpc": "2.0","id":1,"method":"getConfirmedBlock","params":[430, {"encoding": "json","transactionDetails":"full","rewards":false}]} ' ``` @@ -509,7 +512,6 @@ Result: "blockhash": "3Eq21vXNB5s86c62bVuUfTeaMif1N2kUqRPBmGRJhyTA", "parentSlot": 429, "previousBlockhash": "mfcyqEXB3DnHXki6KjjmZck6YjmZLvpAByy2fj4nh6B", - "rewards": [], "transactions": [ { "meta": { diff --git a/transaction-status/src/lib.rs b/transaction-status/src/lib.rs index 10f7d0570a1feb..e5e2ac81b73df0 100644 --- a/transaction-status/src/lib.rs +++ b/transaction-status/src/lib.rs @@ -362,6 +362,48 @@ impl ConfirmedBlock { block_time: self.block_time, } } + + pub fn configure( + self, + encoding: UiTransactionEncoding, + transaction_details: TransactionDetails, + show_rewards: bool, + ) -> UiConfirmedBlock { + let (transactions, signatures) = match transaction_details { + TransactionDetails::Full => ( + Some( + self.transactions + .into_iter() + .map(|tx| tx.encode(encoding)) + .collect(), + ), + None, + ), + TransactionDetails::Signatures => ( + None, + Some( + self.transactions + .into_iter() + .map(|tx| tx.transaction.signatures[0].to_string()) + .collect(), + ), + ), + TransactionDetails::None => (None, None), + }; + UiConfirmedBlock { + previous_blockhash: self.previous_blockhash, + blockhash: self.blockhash, + parent_slot: self.parent_slot, + transactions, + signatures, + rewards: if show_rewards { + Some(self.rewards) + } else { + None + }, + block_time: self.block_time, + } + } } #[derive(Debug, PartialEq, Serialize, Deserialize)] @@ -375,6 +417,49 @@ pub struct EncodedConfirmedBlock { pub block_time: Option, } +#[derive(Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UiConfirmedBlock { + pub previous_blockhash: String, + pub blockhash: String, + pub parent_slot: Slot, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub transactions: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub signatures: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub rewards: Option, + pub block_time: Option, +} + +impl From for UiConfirmedBlock { + fn from(block: EncodedConfirmedBlock) -> Self { + Self { + previous_blockhash: block.previous_blockhash, + blockhash: block.blockhash, + parent_slot: block.parent_slot, + transactions: Some(block.transactions), + signatures: None, + rewards: Some(block.rewards), + block_time: block.block_time, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum TransactionDetails { + Full, + Signatures, + None, +} + +impl Default for TransactionDetails { + fn default() -> Self { + Self::Full + } +} + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ConfirmedTransaction {