diff --git a/client/src/mock_sender.rs b/client/src/mock_sender.rs index bea3a9e99b..faf87a0bcc 100644 --- a/client/src/mock_sender.rs +++ b/client/src/mock_sender.rs @@ -8,8 +8,9 @@ use { rpc_response::{ Response, RpcAccountBalance, RpcBlockProduction, RpcBlockProductionRange, RpcBlockhash, RpcConfirmedTransactionStatusWithSignature, RpcContactInfo, RpcFees, RpcPerfSample, - RpcResponseContext, RpcSimulateTransactionResult, RpcStakeActivation, RpcSupply, - RpcVersionInfo, RpcVoteAccountInfo, RpcVoteAccountStatus, StakeActivationState, + RpcResponseContext, RpcSimulateTransactionResult, RpcSnapshotSlotInfo, + RpcStakeActivation, RpcSupply, RpcVersionInfo, RpcVoteAccountInfo, + RpcVoteAccountStatus, StakeActivationState, }, rpc_sender::RpcSender, }, @@ -222,6 +223,10 @@ impl RpcSender for MockSender { "getMaxShredInsertSlot" => json![0], "requestAirdrop" => Value::String(Signature::new(&[8; 64]).to_string()), "getSnapshotSlot" => Value::Number(Number::from(0)), + "getHighestSnapshotSlot" => json!(RpcSnapshotSlotInfo { + full: 100, + incremental: Some(110), + }), "getBlockHeight" => Value::Number(Number::from(1234)), "getSlotLeaders" => json!([PUBKEY]), "getBlockProduction" => { diff --git a/client/src/rpc_client.rs b/client/src/rpc_client.rs index 5cd74d24dc..112c49ab2c 100644 --- a/client/src/rpc_client.rs +++ b/client/src/rpc_client.rs @@ -1018,13 +1018,16 @@ impl RpcClient { ) } - /// Returns the highest slot that the node has a snapshot for. + /// Returns the highest slot information that the node has snapshots for. + /// + /// This will find the highest full snapshot slot, and the highest incremental snapshot slot + /// _based on_ the full snapshot slot, if there is one. /// /// # RPC Reference /// - /// This method corresponds directly to the [`getSnapshotSlot`] RPC method. + /// This method corresponds directly to the [`getHighestSnapshotSlot`] RPC method. /// - /// [`getSnapshotSlot`]: https://docs.solana.com/developing/clients/jsonrpc-api#getsnapshotslot + /// [`getHighestSnapshotSlot`]: https://docs.solana.com/developing/clients/jsonrpc-api#gethighestsnapshotslot /// /// # Examples /// @@ -1034,9 +1037,18 @@ impl RpcClient { /// # client_error::ClientError, /// # }; /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); - /// let slot = rpc_client.get_snapshot_slot()?; + /// let snapshot_slot_info = rpc_client.get_highest_snapshot_slot()?; /// # Ok::<(), ClientError>(()) /// ``` + pub fn get_highest_snapshot_slot(&self) -> ClientResult { + self.send(RpcRequest::GetHighestSnapshotSlot, Value::Null) + } + + #[deprecated( + since = "1.8.0", + note = "Please use RpcClient::get_highest_snapshot_slot() instead" + )] + #[allow(deprecated)] pub fn get_snapshot_slot(&self) -> ClientResult { self.send(RpcRequest::GetSnapshotSlot, Value::Null) } diff --git a/client/src/rpc_request.rs b/client/src/rpc_request.rs index 0599005501..eb3329e30d 100644 --- a/client/src/rpc_request.rs +++ b/client/src/rpc_request.rs @@ -79,6 +79,11 @@ pub enum RpcRequest { )] GetRecentBlockhash, GetRecentPerformanceSamples, + GetHighestSnapshotSlot, + #[deprecated( + since = "1.8.0", + note = "Please use RpcRequest::GetHighestSnapshotSlot instead" + )] GetSnapshotSlot, GetSignaturesForAddress, GetSignatureStatuses, @@ -151,6 +156,7 @@ impl fmt::Display for RpcRequest { RpcRequest::GetProgramAccounts => "getProgramAccounts", RpcRequest::GetRecentBlockhash => "getRecentBlockhash", RpcRequest::GetRecentPerformanceSamples => "getRecentPerformanceSamples", + RpcRequest::GetHighestSnapshotSlot => "getHighestSnapshotSlot", RpcRequest::GetSnapshotSlot => "getSnapshotSlot", RpcRequest::GetSignaturesForAddress => "getSignaturesForAddress", RpcRequest::GetSignatureStatuses => "getSignatureStatuses", diff --git a/client/src/rpc_response.rs b/client/src/rpc_response.rs index 83fe6979de..77f4922ca8 100644 --- a/client/src/rpc_response.rs +++ b/client/src/rpc_response.rs @@ -435,3 +435,9 @@ impl From for RpcConfirmedTransactionSt } } } + +#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)] +pub struct RpcSnapshotSlotInfo { + pub full: Slot, + pub incremental: Option, +} diff --git a/docs/src/developing/clients/jsonrpc-api.md b/docs/src/developing/clients/jsonrpc-api.md index 8941803de2..8ed5c24eeb 100644 --- a/docs/src/developing/clients/jsonrpc-api.md +++ b/docs/src/developing/clients/jsonrpc-api.md @@ -34,6 +34,7 @@ gives a convenient interface for the RPC methods. - [getFirstAvailableBlock](jsonrpc-api.md#getfirstavailableblock) - [getGenesisHash](jsonrpc-api.md#getgenesishash) - [getHealth](jsonrpc-api.md#gethealth) +- [getHighestSnapshotSlot](jsonrpc-api.md#gethighestsnapshotslot) - [getIdentity](jsonrpc-api.md#getidentity) - [getInflationGovernor](jsonrpc-api.md#getinflationgovernor) - [getInflationRate](jsonrpc-api.md#getinflationrate) @@ -53,7 +54,6 @@ gives a convenient interface for the RPC methods. - [getSlotLeader](jsonrpc-api.md#getslotleader) - [getSlotLeaders](jsonrpc-api.md#getslotleaders) - [getStakeActivation](jsonrpc-api.md#getstakeactivation) -- [getSnapshotSlot](jsonrpc-api.md#getsnapshotslot) - [getSupply](jsonrpc-api.md#getsupply) - [getTokenAccountBalance](jsonrpc-api.md#gettokenaccountbalance) - [getTokenAccountsByDelegate](jsonrpc-api.md#gettokenaccountsbydelegate) @@ -101,6 +101,7 @@ Unstable methods may see breaking changes in patch releases and may not be suppo - [getFeeRateGovernor](jsonrpc-api.md#getfeerategovernor) - [getFees](jsonrpc-api.md#getfees) - [getRecentBlockhash](jsonrpc-api.md#getrecentblockhash) +- [getSnapshotSlot](jsonrpc-api.md#getsnapshotslot) ## Request Formatting @@ -1296,6 +1297,46 @@ Unhealthy Result (if additional information is available) } ``` +### getHighestSnapshotSlot + +**NEW: This method is only available in solana-core v1.8 or newer. Please use +[getSnapshotSlot](jsonrpc-api.md#getsnapshotslot) for solana-core v1.7** + +Returns the highest slot information that the node has snapshots for. + +This will find the highest full snapshot slot, and the highest incremental +snapshot slot _based on_ the full snapshot slot, if there is one. + +#### Parameters: + +None + +#### Results: + +- `` + - `full: ` - Highest full snapshot slot + - `incremental: ` - Highest incremental snapshot slot _based on_ `full` + + +#### Example: + +Request: +```bash +curl http://localhost:8899 -X POST -H "Content-Type: application/json" -d ' + {"jsonrpc":"2.0","id":1,"method":"getHighestSnapshotSlot"} +' +``` + +Result: +```json +{"jsonrpc":"2.0","result":{"full":100,"incremental":110},"id":1} +``` + +Result when the node has no snapshot: +```json +{"jsonrpc":"2.0","error":{"code":-32008,"message":"No snapshot"},"id":1} +``` + ### getIdentity Returns the identity pubkey for the current node @@ -2121,38 +2162,6 @@ Result: } ``` - -### getSnapshotSlot - -Returns the highest slot that the node has a snapshot for - -#### Parameters: - -None - -#### Results: - -- `` - Snapshot slot - -#### Example: - -Request: -```bash -curl http://localhost:8899 -X POST -H "Content-Type: application/json" -d ' - {"jsonrpc":"2.0","id":1, "method":"getSnapshotSlot"} -' -``` - -Result: -```json -{"jsonrpc":"2.0","result":100,"id":1} -``` - -Result when the node has no snapshot: -```json -{"jsonrpc":"2.0","error":{"code":-32008,"message":"No snapshot"},"id":1} -``` - ### getSignaturesForAddress **NEW: This method is only available in solana-core v1.7 or newer. Please use @@ -4778,3 +4787,37 @@ Result: "id": 1 } ``` + +### getSnapshotSlot + +**DEPRECATED: Please use [getHighestSnapshotSlot](jsonrpc-api.md#gethighestsnapshotslot) instead** +This method is expected to be removed in solana-core v1.9 + +Returns the highest slot that the node has a snapshot for + +#### Parameters: + +None + +#### Results: + +- `` - Snapshot slot + +#### Example: + +Request: +```bash +curl http://localhost:8899 -X POST -H "Content-Type: application/json" -d ' + {"jsonrpc":"2.0","id":1, "method":"getSnapshotSlot"} +' +``` + +Result: +```json +{"jsonrpc":"2.0","result":100,"id":1} +``` + +Result when the node has no snapshot: +```json +{"jsonrpc":"2.0","error":{"code":-32008,"message":"No snapshot"},"id":1} +``` diff --git a/rpc/src/rpc.rs b/rpc/src/rpc.rs index 8335a278bf..1cd73ca5a7 100644 --- a/rpc/src/rpc.rs +++ b/rpc/src/rpc.rs @@ -2276,8 +2276,8 @@ pub mod rpc_minimal { commitment: Option, ) -> Result; - #[rpc(meta, name = "getSnapshotSlot")] - fn get_snapshot_slot(&self, meta: Self::Metadata) -> Result; + #[rpc(meta, name = "getHighestSnapshotSlot")] + fn get_highest_snapshot_slot(&self, meta: Self::Metadata) -> Result; #[rpc(meta, name = "getTransactionCount")] fn get_transaction_count( @@ -2373,16 +2373,31 @@ pub mod rpc_minimal { Ok(meta.get_block_height(commitment)) } - fn get_snapshot_slot(&self, meta: Self::Metadata) -> Result { - debug!("get_snapshot_slot rpc request received"); + fn get_highest_snapshot_slot(&self, meta: Self::Metadata) -> Result { + debug!("get_highest_snapshot_slot rpc request received"); - meta.snapshot_config - .and_then(|snapshot_config| { - snapshot_utils::get_highest_full_snapshot_archive_slot( - &snapshot_config.snapshot_archives_dir, - ) - }) - .ok_or_else(|| RpcCustomError::NoSnapshot.into()) + if meta.snapshot_config.is_none() { + return Err(RpcCustomError::NoSnapshot.into()); + } + + let snapshot_archives_dir = meta + .snapshot_config + .map(|snapshot_config| snapshot_config.snapshot_archives_dir) + .unwrap(); + + let full_snapshot_slot = + snapshot_utils::get_highest_full_snapshot_archive_slot(&snapshot_archives_dir) + .ok_or(RpcCustomError::NoSnapshot)?; + let incremental_snapshot_slot = + snapshot_utils::get_highest_incremental_snapshot_archive_slot( + &snapshot_archives_dir, + full_snapshot_slot, + ); + + Ok(RpcSnapshotSlotInfo { + full: full_snapshot_slot, + incremental: incremental_snapshot_slot, + }) } fn get_transaction_count( @@ -3686,6 +3701,9 @@ pub mod rpc_deprecated_v1_8 { &self, meta: Self::Metadata, ) -> Result>; + + #[rpc(meta, name = "getSnapshotSlot")] + fn get_snapshot_slot(&self, meta: Self::Metadata) -> Result; } pub struct DeprecatedV1_8Impl; @@ -3729,6 +3747,18 @@ pub mod rpc_deprecated_v1_8 { debug!("get_fee_rate_governor rpc request received"); Ok(meta.get_fee_rate_governor()) } + + fn get_snapshot_slot(&self, meta: Self::Metadata) -> Result { + debug!("get_snapshot_slot rpc request received"); + + meta.snapshot_config + .and_then(|snapshot_config| { + snapshot_utils::get_highest_full_snapshot_archive_slot( + &snapshot_config.snapshot_archives_dir, + ) + }) + .ok_or_else(|| RpcCustomError::NoSnapshot.into()) + } } } diff --git a/rpc/src/rpc_service.rs b/rpc/src/rpc_service.rs index b6c1f4b1b4..1a4dbb6b1c 100644 --- a/rpc/src/rpc_service.rs +++ b/rpc/src/rpc_service.rs @@ -59,7 +59,8 @@ pub struct JsonRpcService { struct RpcRequestMiddleware { ledger_path: PathBuf, - snapshot_archive_path_regex: Regex, + full_snapshot_archive_path_regex: Regex, + incremental_snapshot_archive_path_regex: Regex, snapshot_config: Option, bank_forks: Arc>, health: Arc, @@ -74,8 +75,12 @@ impl RpcRequestMiddleware { ) -> Self { Self { ledger_path, - snapshot_archive_path_regex: Regex::new( - r"^/snapshot-\d+-[[:alnum:]]+\.(tar|tar\.bz2|tar\.zst|tar\.gz)$", + full_snapshot_archive_path_regex: Regex::new( + snapshot_utils::FULL_SNAPSHOT_ARCHIVE_FILENAME_REGEX, + ) + .unwrap(), + incremental_snapshot_archive_path_regex: Regex::new( + snapshot_utils::INCREMENTAL_SNAPSHOT_ARCHIVE_FILENAME_REGEX, ) .unwrap(), snapshot_config, @@ -108,16 +113,23 @@ impl RpcRequestMiddleware { } fn is_file_get_path(&self, path: &str) -> bool { - match path { - DEFAULT_GENESIS_DOWNLOAD_PATH => true, - _ => { - if self.snapshot_config.is_some() { - self.snapshot_archive_path_regex.is_match(path) - } else { - false - } - } + if path == DEFAULT_GENESIS_DOWNLOAD_PATH { + return true; } + + if self.snapshot_config.is_none() { + return false; + } + + let starting_character = '/'; + if !path.starts_with(starting_character) { + return false; + } + + let path = path.trim_start_matches(starting_character); + + self.full_snapshot_archive_path_regex.is_match(path) + || self.incremental_snapshot_archive_path_regex.is_match(path) } #[cfg(unix)] @@ -497,6 +509,7 @@ mod tests { }, }, solana_sdk::{ + clock::Slot, genesis_config::{ClusterType, DEFAULT_GENESIS_ARCHIVE}, signature::Signer, signer::keypair::Keypair, @@ -602,8 +615,8 @@ mod tests { let rrm_with_snapshot_config = RpcRequestMiddleware::new( PathBuf::from("/"), Some(SnapshotConfig { - full_snapshot_archive_interval_slots: 0, - incremental_snapshot_archive_interval_slots: u64::MAX, + full_snapshot_archive_interval_slots: Slot::MAX, + incremental_snapshot_archive_interval_slots: Slot::MAX, snapshot_archives_dir: PathBuf::from("/"), bank_snapshots_dir: PathBuf::from("/"), archive_format: ArchiveFormat::TarBzip2, @@ -622,6 +635,10 @@ mod tests { assert!(!rrm.is_file_get_path( "/snapshot-100-AvFf9oS8A8U78HdjT9YG2sTTThLHJZmhaMn2g8vkWYnr.tar.bz2" )); + assert!(!rrm.is_file_get_path( + "/incremental-snapshot-100-200-AvFf9oS8A8U78HdjT9YG2sTTThLHJZmhaMn2g8vkWYnr.tar.bz2" + )); + assert!(rrm_with_snapshot_config.is_file_get_path( "/snapshot-100-AvFf9oS8A8U78HdjT9YG2sTTThLHJZmhaMn2g8vkWYnr.tar.bz2" )); @@ -633,11 +650,32 @@ mod tests { assert!(rrm_with_snapshot_config .is_file_get_path("/snapshot-100-AvFf9oS8A8U78HdjT9YG2sTTThLHJZmhaMn2g8vkWYnr.tar")); + assert!(rrm_with_snapshot_config.is_file_get_path( + "/incremental-snapshot-100-200-AvFf9oS8A8U78HdjT9YG2sTTThLHJZmhaMn2g8vkWYnr.tar.bz2" + )); + assert!(rrm_with_snapshot_config.is_file_get_path( + "/incremental-snapshot-100-200-AvFf9oS8A8U78HdjT9YG2sTTThLHJZmhaMn2g8vkWYnr.tar.zst" + )); + assert!(rrm_with_snapshot_config.is_file_get_path( + "/incremental-snapshot-100-200-AvFf9oS8A8U78HdjT9YG2sTTThLHJZmhaMn2g8vkWYnr.tar.gz" + )); + assert!(rrm_with_snapshot_config.is_file_get_path( + "/incremental-snapshot-100-200-AvFf9oS8A8U78HdjT9YG2sTTThLHJZmhaMn2g8vkWYnr.tar" + )); + assert!(!rrm_with_snapshot_config.is_file_get_path( "/snapshot-notaslotnumber-AvFf9oS8A8U78HdjT9YG2sTTThLHJZmhaMn2g8vkWYnr.tar.bz2" )); + assert!(!rrm_with_snapshot_config.is_file_get_path( + "/incremental-snapshot-notaslotnumber-200-AvFf9oS8A8U78HdjT9YG2sTTThLHJZmhaMn2g8vkWYnr.tar.bz2" + )); + assert!(!rrm_with_snapshot_config.is_file_get_path( + "/incremental-snapshot-100-notaslotnumber-AvFf9oS8A8U78HdjT9YG2sTTThLHJZmhaMn2g8vkWYnr.tar.bz2" + )); assert!(!rrm_with_snapshot_config.is_file_get_path("../../../test/snapshot-123-xxx.tar")); + assert!(!rrm_with_snapshot_config + .is_file_get_path("../../../test/incremental-snapshot-123-456-xxx.tar")); assert!(!rrm.is_file_get_path("/")); assert!(!rrm.is_file_get_path("..")); diff --git a/validator/src/dashboard.rs b/validator/src/dashboard.rs index 94085fd84f..15469654fd 100644 --- a/validator/src/dashboard.rs +++ b/validator/src/dashboard.rs @@ -113,13 +113,13 @@ impl Dashboard { } let progress_bar = new_spinner_progress_bar(); - let mut snapshot_slot = None; + let mut snapshot_slot_info = None; for i in 0.. { if exit.load(Ordering::Relaxed) { break; } if i % 10 == 0 { - snapshot_slot = rpc_client.get_snapshot_slot().ok(); + snapshot_slot_info = rpc_client.get_highest_snapshot_slot().ok(); } match get_validator_stats(&rpc_client, &identity) { @@ -147,7 +147,7 @@ impl Dashboard { progress_bar.set_message(format!( "{}{}{}| \ Processed Slot: {} | Confirmed Slot: {} | Finalized Slot: {} | \ - Snapshot Slot: {} | \ + Full Snapshot Slot: {} | Incremental Snapshot Slot: {} \ Transactions: {} | {}", uptime, if health == "ok" { @@ -163,9 +163,17 @@ impl Dashboard { processed_slot, confirmed_slot, finalized_slot, - snapshot_slot - .map(|s| s.to_string()) - .unwrap_or_else(|| "-".to_string()), + snapshot_slot_info + .as_ref() + .map(|snapshot_slot_info| snapshot_slot_info.full.to_string()) + .unwrap_or_else(|| '-'.to_string()), + snapshot_slot_info + .as_ref() + .map(|snapshot_slot_info| snapshot_slot_info + .incremental + .map(|incremental| incremental.to_string())) + .flatten() + .unwrap_or_else(|| '-'.to_string()), transaction_count, identity_balance )); diff --git a/validator/src/main.rs b/validator/src/main.rs index 0273016682..a5d0b42d23 100644 --- a/validator/src/main.rs +++ b/validator/src/main.rs @@ -149,7 +149,7 @@ fn wait_for_restart_window( let progress_bar = new_spinner_progress_bar(); let monitor_start_time = SystemTime::now(); loop { - let snapshot_slot = rpc_client.get_snapshot_slot().ok(); + let snapshot_slot_info = rpc_client.get_highest_snapshot_slot().ok(); let epoch_info = rpc_client.get_epoch_info_with_commitment(CommitmentConfig::processed())?; let healthy = rpc_client.get_health().ok().is_some(); let delinquent_stake_percentage = { @@ -278,13 +278,14 @@ fn wait_for_restart_window( } }; + let full_snapshot_slot = + snapshot_slot_info.map(|snapshot_slot_info| snapshot_slot_info.full); match in_leader_schedule_hole { Ok(_) => { if restart_snapshot == None { - restart_snapshot = snapshot_slot; + restart_snapshot = full_snapshot_slot; } - - if restart_snapshot == snapshot_slot && !monitoring_another_validator { + if restart_snapshot == full_snapshot_slot && !monitoring_another_validator { "Waiting for a new snapshot".to_string() } else if delinquent_stake_percentage >= min_delinquency_percentage { style("Delinquency too high").red().to_string() @@ -315,10 +316,10 @@ fn wait_for_restart_window( "".to_string() } else { format!( - "| Snapshot Slot: {}", - snapshot_slot - .map(|s| s.to_string()) - .unwrap_or_else(|| "-".to_string()) + "| Full Snapshot Slot: {}", + snapshot_slot_info + .map(|snapshot_slot_info| snapshot_slot_info.full.to_string()) + .unwrap_or_else(|| '-'.to_string()), ) }, delinquent_stake_percentage * 100.,