Skip to content
This repository has been archived by the owner on Jan 22, 2025. It is now read-only.

cli: Add transaction-history #9614

Merged
merged 4 commits into from
Apr 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,11 @@ pub enum CliCommand {
use_lamports_unit: bool,
commitment_config: CommitmentConfig,
},
TransactionHistory {
address: Pubkey,
end_slot: Option<Slot>, // None == latest slot
slot_limit: u64,
},
// Nonce commands
AuthorizeNonceAccount {
nonce_account: Pubkey,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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

Expand Down
80 changes: 80 additions & 0 deletions cli/src/cluster_query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -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"
),
),
)
}
}

Expand Down Expand Up @@ -436,6 +470,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
fn new_spinner_progress_bar() -> ProgressBar {
let progress_bar = ProgressBar::new(42);
Expand Down Expand Up @@ -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<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
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is one subcommand for which it could be useful to return json. Could we create a Cli type and impl Display for niceties like this print statement and the signatures count below? (a la #9478 )

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about that but didn't go for it since this command really is just cli window dressing over the same JSON RPC API. But my mind is open to be convinced

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, fair. I think since this has the potential to return a whole heap of data, request for json might come up, but we can refactor then.

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::*;
Expand Down
19 changes: 15 additions & 4 deletions client/src/rpc_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> =
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::<Signature>().map_err(|err| {
Into::<ClientError>::into(RpcError::ParseError(err.to_string()))
})?,
);
}
Ok(signatures)
}

pub fn get_confirmed_transaction(
Expand Down
3 changes: 3 additions & 0 deletions client/src/rpc_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to move this around the tree a little


impl RpcRequest {
pub(crate) fn build_request_json(&self, id: u64, params: Value) -> Value {
let jsonrpc = "2.0";
Expand Down
18 changes: 10 additions & 8 deletions core/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -38,9 +43,6 @@ use std::{
time::{Duration, Instant},
};

const MAX_QUERY_ITEMS: usize = 256;
const MAX_SLOT_RANGE: u64 = 10_000;

type RpcResponse<T> = Result<Response<T>>;

fn new_response<T>(bank: &Bank, value: T) -> RpcResponse<T> {
Expand Down Expand Up @@ -1058,10 +1060,10 @@ impl RpcSol for RpcSolImpl {
signature_strs: Vec<String>,
config: Option<RpcSignatureStatusConfig>,
) -> 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!(
"Too many inputs provided; max {}",
MAX_QUERY_ITEMS
MAX_GET_SIGNATURE_STATUSES_QUERY_ITEMS
)));
}
let mut signatures: Vec<Signature> = vec![];
Expand Down Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions ledger-tool/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,17 @@ fn analyze_storage(database: &Database) -> Result<(), String> {
"TransactionStatus",
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(())
}
Expand Down