use {
super::FetchTuning,
crate::{
config::{get_solana_cfg_url, Config, ConfigOverride},
fetch::pmp::pmp_metadata_address,
get_cluster_and_wallet,
},
anyhow::{anyhow, Result},
solana_commitment_config::CommitmentConfig,
solana_pubkey::Pubkey,
solana_rpc_client::rpc_client::{GetConfirmedSignaturesForAddress2Config, RpcClient},
solana_rpc_client_api::{
client_error::{reqwest::StatusCode, ErrorKind as RpcClientErrorKind},
config::RpcTransactionConfig,
response::RpcConfirmedTransactionStatusWithSignature,
},
solana_signature::Signature,
solana_transaction_status_client_types::{
EncodedConfirmedTransactionWithStatusMeta, UiTransactionEncoding,
},
std::str::FromStr,
};
const IDL_SIGNATURE_PAGE_SIZE: usize = 100;
pub(super) fn create_rpc_client(cfg_override: &ConfigOverride) -> Result<RpcClient> {
let workspace_cluster = Config::discover(cfg_override)?
.map(|_| get_cluster_and_wallet(cfg_override))
.transpose()?;
let url = match workspace_cluster {
Some(cluster_url) => cluster_url.0,
None => {
if let Some(cluster) = cfg_override.cluster.as_ref() {
cluster.url().to_string()
} else {
get_solana_cfg_url()?
}
}
};
Ok(crate::create_client(url))
}
pub(super) fn fetch_idl_signatures(
client: &RpcClient,
address: &Pubkey,
before_timestamp: Option<i64>,
after_timestamp: Option<i64>,
target_slot: Option<u64>,
max_signatures: usize,
) -> Result<Vec<RpcConfirmedTransactionStatusWithSignature>> {
let program_signer = Pubkey::find_program_address(&[], address).0;
let idl_account_address = Pubkey::create_with_seed(&program_signer, "anchor:idl", address)
.map_err(|e| anyhow!("Failed to derive IDL account address: {e}"))?;
fetch_signatures_for_address(
client,
&idl_account_address,
before_timestamp,
after_timestamp,
target_slot,
max_signatures,
)
}
pub(super) fn fetch_pmp_idl_signatures(
client: &RpcClient,
program_id: &Pubkey,
authority: Option<&Pubkey>,
before_timestamp: Option<i64>,
after_timestamp: Option<i64>,
target_slot: Option<u64>,
max_signatures: usize,
) -> Result<Vec<RpcConfirmedTransactionStatusWithSignature>> {
let metadata_address = pmp_metadata_address(program_id, authority);
fetch_signatures_for_address(
client,
&metadata_address,
before_timestamp,
after_timestamp,
target_slot,
max_signatures,
)
}
pub(super) fn fetch_signatures_for_address(
client: &RpcClient,
address: &Pubkey,
before_timestamp: Option<i64>,
after_timestamp: Option<i64>,
target_slot: Option<u64>,
max_signatures: usize,
) -> Result<Vec<RpcConfirmedTransactionStatusWithSignature>> {
let mut signatures = Vec::new();
let mut cursor: Option<Signature> = None;
let mut cap_reached = false;
loop {
let config = GetConfirmedSignaturesForAddress2Config {
before: cursor,
until: None,
limit: Some(IDL_SIGNATURE_PAGE_SIZE),
commitment: Some(client.commitment()),
};
let page = client.get_signatures_for_address_with_config(address, config)?;
if page.is_empty() {
break;
}
let next_cursor = page
.last()
.and_then(|sig| Signature::from_str(&sig.signature).ok());
let has_date_filter = before_timestamp.is_some() || after_timestamp.is_some();
let mut crossed_after_bound = false;
for sig in page {
if sig.err.is_some() {
continue;
}
if target_slot.is_some_and(|slot| sig.slot > slot) {
continue;
}
if has_date_filter {
let Some(bt) = sig.block_time else { continue };
if before_timestamp.is_some_and(|ts| bt > ts) {
continue;
}
if after_timestamp.is_some_and(|ts| bt < ts) {
crossed_after_bound = true;
continue;
}
}
if signatures.len() >= max_signatures {
cap_reached = true;
break;
}
signatures.push(sig);
}
if cap_reached {
break;
}
if crossed_after_bound {
break;
}
match next_cursor {
Some(sig) => cursor = Some(sig),
None => break,
}
}
if cap_reached {
eprintln!(
"warning: reached --max-signatures cap of {} for account {}; older history was not \
scanned. Pass a higher --max-signatures to fetch more.",
max_signatures, address
);
}
Ok(signatures)
}
pub(super) fn fetch_transaction(
client: &RpcClient,
signature: &Signature,
tuning: &FetchTuning,
) -> Result<EncodedConfirmedTransactionWithStatusMeta> {
let config = RpcTransactionConfig {
encoding: Some(UiTransactionEncoding::Json),
commitment: Some(CommitmentConfig::confirmed()),
max_supported_transaction_version: Some(0),
};
let mut attempt: u32 = 0;
loop {
attempt += 1;
match client.get_transaction_with_config(signature, config) {
Ok(tx) => return Ok(tx),
Err(e) => {
let retryable = matches!(
e.kind(),
RpcClientErrorKind::Reqwest(error)
if error.status() == Some(StatusCode::TOO_MANY_REQUESTS)
);
if !retryable || attempt >= tuning.max_retries {
return Err(anyhow!("failed to fetch transaction {signature}: {e}"));
}
let shift = (attempt - 1).min(20);
let backoff = tuning.retry_backoff_ms.saturating_mul(1u64 << shift);
std::thread::sleep(std::time::Duration::from_millis(backoff));
}
}
}
}