| @@ -227,6 +227,11 @@ pub enum CliCommand { | |||||||
|         use_lamports_unit: bool, |         use_lamports_unit: bool, | ||||||
|         commitment_config: CommitmentConfig, |         commitment_config: CommitmentConfig, | ||||||
|     }, |     }, | ||||||
|  |     TransactionHistory { | ||||||
|  |         address: Pubkey, | ||||||
|  |         end_slot: Option<Slot>, // None == latest slot | ||||||
|  |         slot_limit: u64, | ||||||
|  |     }, | ||||||
|     // Nonce commands |     // Nonce commands | ||||||
|     AuthorizeNonceAccount { |     AuthorizeNonceAccount { | ||||||
|         nonce_account: Pubkey, |         nonce_account: Pubkey, | ||||||
| @@ -618,6 +623,9 @@ pub fn parse_command( | |||||||
|         }), |         }), | ||||||
|         ("stakes", Some(matches)) => parse_show_stakes(matches, wallet_manager), |         ("stakes", Some(matches)) => parse_show_stakes(matches, wallet_manager), | ||||||
|         ("validators", Some(matches)) => parse_show_validators(matches), |         ("validators", Some(matches)) => parse_show_validators(matches), | ||||||
|  |         ("transaction-history", Some(matches)) => { | ||||||
|  |             parse_transaction_history(matches, wallet_manager) | ||||||
|  |         } | ||||||
|         // Nonce Commands |         // Nonce Commands | ||||||
|         ("authorize-nonce-account", Some(matches)) => { |         ("authorize-nonce-account", Some(matches)) => { | ||||||
|             parse_authorize_nonce_account(matches, default_signer_path, wallet_manager) |             parse_authorize_nonce_account(matches, default_signer_path, wallet_manager) | ||||||
| @@ -1729,6 +1737,11 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { | |||||||
|             use_lamports_unit, |             use_lamports_unit, | ||||||
|             commitment_config, |             commitment_config, | ||||||
|         } => process_show_validators(&rpc_client, config, *use_lamports_unit, *commitment_config), |         } => process_show_validators(&rpc_client, config, *use_lamports_unit, *commitment_config), | ||||||
|  |         CliCommand::TransactionHistory { | ||||||
|  |             address, | ||||||
|  |             end_slot, | ||||||
|  |             slot_limit, | ||||||
|  |         } => process_transaction_history(&rpc_client, address, *end_slot, *slot_limit), | ||||||
|  |  | ||||||
|         // Nonce Commands |         // Nonce Commands | ||||||
|  |  | ||||||
|   | |||||||
| @@ -14,6 +14,7 @@ use solana_clap_utils::{input_parsers::*, input_validators::*, keypair::signer_f | |||||||
| use solana_client::{ | use solana_client::{ | ||||||
|     pubsub_client::{PubsubClient, SlotInfoMessage}, |     pubsub_client::{PubsubClient, SlotInfoMessage}, | ||||||
|     rpc_client::RpcClient, |     rpc_client::RpcClient, | ||||||
|  |     rpc_request::MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS_SLOT_RANGE, | ||||||
| }; | }; | ||||||
| use solana_remote_wallet::remote_wallet::RemoteWalletManager; | use solana_remote_wallet::remote_wallet::RemoteWalletManager; | ||||||
| use solana_sdk::{ | use solana_sdk::{ | ||||||
| @@ -277,6 +278,40 @@ impl ClusterQuerySubCommands for App<'_, '_> { | |||||||
|                         .help("Display balance in lamports instead of SOL"), |                         .help("Display balance in lamports instead of SOL"), | ||||||
|                 ), |                 ), | ||||||
|         ) |         ) | ||||||
|  |         .subcommand( | ||||||
|  |             SubCommand::with_name("transaction-history") | ||||||
|  |                 .about("Show historical transactions affecting the given address, \ | ||||||
|  |                        ordered based on the slot in which they were confirmed in \ | ||||||
|  |                        from lowest to highest slot") | ||||||
|  |                 .arg( | ||||||
|  |                     Arg::with_name("address") | ||||||
|  |                         .index(1) | ||||||
|  |                         .value_name("ADDRESS") | ||||||
|  |                         .required(true) | ||||||
|  |                         .validator(is_valid_pubkey) | ||||||
|  |                         .help("Account address"), | ||||||
|  |                 ) | ||||||
|  |                 .arg( | ||||||
|  |                     Arg::with_name("end_slot") | ||||||
|  |                         .takes_value(false) | ||||||
|  |                         .value_name("SLOT") | ||||||
|  |                         .index(2) | ||||||
|  |                         .validator(is_slot) | ||||||
|  |                         .help( | ||||||
|  |                             "Slot to start from [default: latest slot at maximum commitment]" | ||||||
|  |                         ), | ||||||
|  |                 ) | ||||||
|  |                 .arg( | ||||||
|  |                     Arg::with_name("limit") | ||||||
|  |                         .long("limit") | ||||||
|  |                         .takes_value(true) | ||||||
|  |                         .value_name("NUMBER OF SLOTS") | ||||||
|  |                         .validator(is_slot) | ||||||
|  |                         .help( | ||||||
|  |                             "Limit the search to this many slots" | ||||||
|  |                         ), | ||||||
|  |                 ), | ||||||
|  |         ) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -440,6 +475,25 @@ pub fn parse_show_validators(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, | |||||||
|     }) |     }) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | pub fn parse_transaction_history( | ||||||
|  |     matches: &ArgMatches<'_>, | ||||||
|  |     wallet_manager: &mut Option<Arc<RemoteWalletManager>>, | ||||||
|  | ) -> Result<CliCommandInfo, CliError> { | ||||||
|  |     let address = pubkey_of_signer(matches, "address", wallet_manager)?.unwrap(); | ||||||
|  |     let end_slot = value_t!(matches, "end_slot", Slot).ok(); | ||||||
|  |     let slot_limit = value_t!(matches, "limit", u64) | ||||||
|  |         .unwrap_or(MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS_SLOT_RANGE); | ||||||
|  |  | ||||||
|  |     Ok(CliCommandInfo { | ||||||
|  |         command: CliCommand::TransactionHistory { | ||||||
|  |             address, | ||||||
|  |             end_slot, | ||||||
|  |             slot_limit, | ||||||
|  |         }, | ||||||
|  |         signers: vec![], | ||||||
|  |     }) | ||||||
|  | } | ||||||
|  |  | ||||||
| /// Creates a new process bar for processing that will take an unknown amount of time | /// Creates a new process bar for processing that will take an unknown amount of time | ||||||
| fn new_spinner_progress_bar() -> ProgressBar { | fn new_spinner_progress_bar() -> ProgressBar { | ||||||
|     let progress_bar = ProgressBar::new(42); |     let progress_bar = ProgressBar::new(42); | ||||||
| @@ -1174,6 +1228,33 @@ pub fn process_show_validators( | |||||||
|     Ok("".to_string()) |     Ok("".to_string()) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | pub fn process_transaction_history( | ||||||
|  |     rpc_client: &RpcClient, | ||||||
|  |     address: &Pubkey, | ||||||
|  |     end_slot: Option<Slot>, // None == use latest slot | ||||||
|  |     slot_limit: u64, | ||||||
|  | ) -> ProcessResult { | ||||||
|  |     let end_slot = { | ||||||
|  |         if let Some(end_slot) = end_slot { | ||||||
|  |             end_slot | ||||||
|  |         } else { | ||||||
|  |             rpc_client.get_slot_with_commitment(CommitmentConfig::max())? | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  |     let start_slot = end_slot.saturating_sub(slot_limit); | ||||||
|  |  | ||||||
|  |     println!( | ||||||
|  |         "Transactions affecting {} within slots [{},{}]", | ||||||
|  |         address, start_slot, end_slot | ||||||
|  |     ); | ||||||
|  |     let signatures = | ||||||
|  |         rpc_client.get_confirmed_signatures_for_address(address, start_slot, end_slot)?; | ||||||
|  |     for signature in &signatures { | ||||||
|  |         println!("{}", signature); | ||||||
|  |     } | ||||||
|  |     Ok(format!("{} transactions found", signatures.len(),)) | ||||||
|  | } | ||||||
|  |  | ||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| mod tests { | mod tests { | ||||||
|     use super::*; |     use super::*; | ||||||
|   | |||||||
| @@ -289,14 +289,25 @@ impl RpcClient { | |||||||
|             .client |             .client | ||||||
|             .send( |             .send( | ||||||
|                 &RpcRequest::GetConfirmedSignaturesForAddress, |                 &RpcRequest::GetConfirmedSignaturesForAddress, | ||||||
|                 json!([address, start_slot, end_slot]), |                 json!([address.to_string(), start_slot, end_slot]), | ||||||
|                 0, |                 0, | ||||||
|             ) |             ) | ||||||
|             .map_err(|err| err.into_with_command("GetConfirmedSignaturesForAddress"))?; |             .map_err(|err| err.into_with_command("GetConfirmedSignaturesForAddress"))?; | ||||||
|  |  | ||||||
|  |         let signatures_base58_str: Vec<String> = | ||||||
|             serde_json::from_value(response).map_err(|err| { |             serde_json::from_value(response).map_err(|err| { | ||||||
|                 ClientError::new_with_command(err.into(), "GetConfirmedSignaturesForAddress") |                 ClientError::new_with_command(err.into(), "GetConfirmedSignaturesForAddress") | ||||||
|         }) |             })?; | ||||||
|  |  | ||||||
|  |         let mut signatures = vec![]; | ||||||
|  |         for signature_base58_str in signatures_base58_str { | ||||||
|  |             signatures.push( | ||||||
|  |                 signature_base58_str.parse::<Signature>().map_err(|err| { | ||||||
|  |                     Into::<ClientError>::into(RpcError::ParseError(err.to_string())) | ||||||
|  |                 })?, | ||||||
|  |             ); | ||||||
|  |         } | ||||||
|  |         Ok(signatures) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn get_confirmed_transaction( |     pub fn get_confirmed_transaction( | ||||||
|   | |||||||
| @@ -42,6 +42,9 @@ pub enum RpcRequest { | |||||||
|     MinimumLedgerSlot, |     MinimumLedgerSlot, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | pub const MAX_GET_SIGNATURE_STATUSES_QUERY_ITEMS: usize = 256; | ||||||
|  | pub const MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS_SLOT_RANGE: u64 = 10_000; | ||||||
|  |  | ||||||
| impl RpcRequest { | impl RpcRequest { | ||||||
|     pub(crate) fn build_request_json(&self, id: u64, params: Value) -> Value { |     pub(crate) fn build_request_json(&self, id: u64, params: Value) -> Value { | ||||||
|         let jsonrpc = "2.0"; |         let jsonrpc = "2.0"; | ||||||
|   | |||||||
| @@ -10,7 +10,12 @@ use crate::{ | |||||||
| use bincode::serialize; | use bincode::serialize; | ||||||
| use jsonrpc_core::{Error, Metadata, Result}; | use jsonrpc_core::{Error, Metadata, Result}; | ||||||
| use jsonrpc_derive::rpc; | use jsonrpc_derive::rpc; | ||||||
| use solana_client::rpc_response::*; | use solana_client::{ | ||||||
|  |     rpc_request::{ | ||||||
|  |         MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS_SLOT_RANGE, MAX_GET_SIGNATURE_STATUSES_QUERY_ITEMS, | ||||||
|  |     }, | ||||||
|  |     rpc_response::*, | ||||||
|  | }; | ||||||
| use solana_faucet::faucet::request_airdrop_transaction; | use solana_faucet::faucet::request_airdrop_transaction; | ||||||
| use solana_ledger::{ | use solana_ledger::{ | ||||||
|     bank_forks::BankForks, blockstore::Blockstore, rooted_slot_iterator::RootedSlotIterator, |     bank_forks::BankForks, blockstore::Blockstore, rooted_slot_iterator::RootedSlotIterator, | ||||||
| @@ -41,9 +46,6 @@ use std::{ | |||||||
|     time::{Duration, Instant}, |     time::{Duration, Instant}, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const MAX_QUERY_ITEMS: usize = 256; |  | ||||||
| const MAX_SLOT_RANGE: u64 = 10_000; |  | ||||||
|  |  | ||||||
| type RpcResponse<T> = Result<Response<T>>; | type RpcResponse<T> = Result<Response<T>>; | ||||||
|  |  | ||||||
| fn new_response<T>(bank: &Bank, value: T) -> RpcResponse<T> { | fn new_response<T>(bank: &Bank, value: T) -> RpcResponse<T> { | ||||||
| @@ -1072,10 +1074,10 @@ impl RpcSol for RpcSolImpl { | |||||||
|         signature_strs: Vec<String>, |         signature_strs: Vec<String>, | ||||||
|         config: Option<RpcSignatureStatusConfig>, |         config: Option<RpcSignatureStatusConfig>, | ||||||
|     ) -> RpcResponse<Vec<Option<TransactionStatus>>> { |     ) -> RpcResponse<Vec<Option<TransactionStatus>>> { | ||||||
|         if signature_strs.len() > MAX_QUERY_ITEMS { |         if signature_strs.len() > MAX_GET_SIGNATURE_STATUSES_QUERY_ITEMS { | ||||||
|             return Err(Error::invalid_params(format!( |             return Err(Error::invalid_params(format!( | ||||||
|                 "Too many inputs provided; max {}", |                 "Too many inputs provided; max {}", | ||||||
|                 MAX_QUERY_ITEMS |                 MAX_GET_SIGNATURE_STATUSES_QUERY_ITEMS | ||||||
|             ))); |             ))); | ||||||
|         } |         } | ||||||
|         let mut signatures: Vec<Signature> = vec![]; |         let mut signatures: Vec<Signature> = vec![]; | ||||||
| @@ -1374,10 +1376,10 @@ impl RpcSol for RpcSolImpl { | |||||||
|                 start_slot, end_slot |                 start_slot, end_slot | ||||||
|             ))); |             ))); | ||||||
|         } |         } | ||||||
|         if end_slot - start_slot > MAX_SLOT_RANGE { |         if end_slot - start_slot > MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS_SLOT_RANGE { | ||||||
|             return Err(Error::invalid_params(format!( |             return Err(Error::invalid_params(format!( | ||||||
|                 "Slot range too large; max {}", |                 "Slot range too large; max {}", | ||||||
|                 MAX_SLOT_RANGE |                 MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS_SLOT_RANGE | ||||||
|             ))); |             ))); | ||||||
|         } |         } | ||||||
|         meta.request_processor |         meta.request_processor | ||||||
|   | |||||||
| @@ -480,6 +480,17 @@ fn analyze_storage(database: &Database) -> Result<(), String> { | |||||||
|         "TransactionStatus", |         "TransactionStatus", | ||||||
|         TransactionStatus::key_size(), |         TransactionStatus::key_size(), | ||||||
|     )?; |     )?; | ||||||
|  |     analyze_column::<TransactionStatus>( | ||||||
|  |         database, | ||||||
|  |         "TransactionStatusIndex", | ||||||
|  |         TransactionStatusIndex::key_size(), | ||||||
|  |     )?; | ||||||
|  |     analyze_column::<AddressSignatures>( | ||||||
|  |         database, | ||||||
|  |         "AddressSignatures", | ||||||
|  |         AddressSignatures::key_size(), | ||||||
|  |     )?; | ||||||
|  |     analyze_column::<Rewards>(database, "Rewards", Rewards::key_size())?; | ||||||
|  |  | ||||||
|     Ok(()) |     Ok(()) | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user