@ -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::{
|
||||||
@ -273,6 +274,39 @@ 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(
|
||||||
|
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<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);
|
||||||
@ -1170,6 +1223,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";
|
||||||
|
@ -7,7 +7,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,
|
||||||
@ -38,9 +43,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> {
|
||||||
@ -1058,10 +1060,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![];
|
||||||
@ -1360,10 +1362,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
|
||||||
|
@ -499,6 +499,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