diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 59baa2d96..9fd2d752c 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -227,6 +227,11 @@ pub enum CliCommand { use_lamports_unit: bool, commitment_config: CommitmentConfig, }, + TransactionHistory { + address: Pubkey, + end_slot: Option, // None == latest slot + slot_limit: u64, + }, // Nonce commands AuthorizeNonceAccount { nonce_account: Pubkey, @@ -618,6 +623,9 @@ pub fn parse_command( }), ("stakes", Some(matches)) => parse_show_stakes(matches, wallet_manager), ("validators", Some(matches)) => parse_show_validators(matches), + ("transaction-history", Some(matches)) => { + parse_transaction_history(matches, wallet_manager) + } // Nonce Commands ("authorize-nonce-account", Some(matches)) => { parse_authorize_nonce_account(matches, default_signer_path, wallet_manager) @@ -1729,6 +1737,11 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { 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 diff --git a/cli/src/cluster_query.rs b/cli/src/cluster_query.rs index 4ae570d2c..44f139058 100644 --- a/cli/src/cluster_query.rs +++ b/cli/src/cluster_query.rs @@ -14,6 +14,7 @@ use solana_clap_utils::{input_parsers::*, input_validators::*, keypair::signer_f use solana_client::{ pubsub_client::{PubsubClient, SlotInfoMessage}, rpc_client::RpcClient, + rpc_request::MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS_SLOT_RANGE, }; use solana_remote_wallet::remote_wallet::RemoteWalletManager; use solana_sdk::{ @@ -273,6 +274,39 @@ impl ClusterQuerySubCommands for App<'_, '_> { .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( + pubkey!(Arg::with_name("address") + .index(1) + .value_name("ADDRESS") + .required(true), + "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" + ), + ), + ) } } @@ -436,6 +470,25 @@ pub fn parse_show_validators(matches: &ArgMatches<'_>) -> Result, + wallet_manager: &mut Option>, +) -> Result { + 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 fn new_spinner_progress_bar() -> ProgressBar { let progress_bar = ProgressBar::new(42); @@ -1170,6 +1223,33 @@ pub fn process_show_validators( Ok("".to_string()) } +pub fn process_transaction_history( + rpc_client: &RpcClient, + address: &Pubkey, + end_slot: Option, // 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)] mod tests { use super::*; diff --git a/client/src/rpc_client.rs b/client/src/rpc_client.rs index 2f3feef2a..f94958491 100644 --- a/client/src/rpc_client.rs +++ b/client/src/rpc_client.rs @@ -289,14 +289,25 @@ impl RpcClient { .client .send( &RpcRequest::GetConfirmedSignaturesForAddress, - json!([address, start_slot, end_slot]), + json!([address.to_string(), start_slot, end_slot]), 0, ) .map_err(|err| err.into_with_command("GetConfirmedSignaturesForAddress"))?; - serde_json::from_value(response).map_err(|err| { - ClientError::new_with_command(err.into(), "GetConfirmedSignaturesForAddress") - }) + let signatures_base58_str: Vec = + serde_json::from_value(response).map_err(|err| { + 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::().map_err(|err| { + Into::::into(RpcError::ParseError(err.to_string())) + })?, + ); + } + Ok(signatures) } pub fn get_confirmed_transaction( diff --git a/client/src/rpc_request.rs b/client/src/rpc_request.rs index c9e7e9b35..bdac2335a 100644 --- a/client/src/rpc_request.rs +++ b/client/src/rpc_request.rs @@ -42,6 +42,9 @@ pub enum RpcRequest { 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 { pub(crate) fn build_request_json(&self, id: u64, params: Value) -> Value { let jsonrpc = "2.0"; diff --git a/core/src/rpc.rs b/core/src/rpc.rs index 12907cae0..a64f74bec 100644 --- a/core/src/rpc.rs +++ b/core/src/rpc.rs @@ -7,7 +7,12 @@ use crate::{ use bincode::serialize; use jsonrpc_core::{Error, Metadata, Result}; 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_ledger::{ bank_forks::BankForks, blockstore::Blockstore, rooted_slot_iterator::RootedSlotIterator, @@ -38,9 +43,6 @@ use std::{ time::{Duration, Instant}, }; -const MAX_QUERY_ITEMS: usize = 256; -const MAX_SLOT_RANGE: u64 = 10_000; - type RpcResponse = Result>; fn new_response(bank: &Bank, value: T) -> RpcResponse { @@ -1058,10 +1060,10 @@ impl RpcSol for RpcSolImpl { signature_strs: Vec, config: Option, ) -> RpcResponse>> { - if signature_strs.len() > MAX_QUERY_ITEMS { + if signature_strs.len() > MAX_GET_SIGNATURE_STATUSES_QUERY_ITEMS { return Err(Error::invalid_params(format!( "Too many inputs provided; max {}", - MAX_QUERY_ITEMS + MAX_GET_SIGNATURE_STATUSES_QUERY_ITEMS ))); } let mut signatures: Vec = vec![]; @@ -1360,10 +1362,10 @@ impl RpcSol for RpcSolImpl { 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!( "Slot range too large; max {}", - MAX_SLOT_RANGE + MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS_SLOT_RANGE ))); } meta.request_processor diff --git a/ledger-tool/src/main.rs b/ledger-tool/src/main.rs index 0220fda8e..40b87e891 100644 --- a/ledger-tool/src/main.rs +++ b/ledger-tool/src/main.rs @@ -499,6 +499,17 @@ fn analyze_storage(database: &Database) -> Result<(), String> { "TransactionStatus", TransactionStatus::key_size(), )?; + analyze_column::( + database, + "TransactionStatusIndex", + TransactionStatusIndex::key_size(), + )?; + analyze_column::( + database, + "AddressSignatures", + AddressSignatures::key_size(), + )?; + analyze_column::(database, "Rewards", Rewards::key_size())?; Ok(()) }