diff --git a/client/src/mock_sender.rs b/client/src/mock_sender.rs index 91a29bf0eaa67c..1f32038486670f 100644 --- a/client/src/mock_sender.rs +++ b/client/src/mock_sender.rs @@ -2,23 +2,15 @@ use { crate::{ - client_error::Result, - rpc_config::RpcBlockProductionConfig, - rpc_request::RpcRequest, - rpc_response::{ - Response, RpcAccountBalance, RpcBlockProduction, RpcBlockProductionRange, RpcBlockhash, - RpcConfirmedTransactionStatusWithSignature, RpcContactInfo, RpcFees, RpcPerfSample, - RpcResponseContext, RpcSimulateTransactionResult, RpcSnapshotSlotInfo, - RpcStakeActivation, RpcSupply, RpcVersionInfo, RpcVoteAccountInfo, - RpcVoteAccountStatus, StakeActivationState, - }, - rpc_sender::*, + client_error::Result, rpc_config::RpcBlockProductionConfig, rpc_request::RpcRequest, + rpc_response::*, rpc_sender::*, }, serde_json::{json, Number, Value}, solana_sdk::{ clock::{Slot, UnixTimestamp}, epoch_info::EpochInfo, fee_calculator::{FeeCalculator, FeeRateGovernor}, + hash::Hash, instruction::InstructionError, message::MessageHeader, signature::Signature, @@ -231,6 +223,19 @@ impl RpcSender for MockSender { full: 100, incremental: Some(110), }), + "getSnapshotInfo" => { + json!(RpcSnapshotInfo::IncrementalSnapshot { + full: SnapshotInfo { + slot: 8_000, + hash: Hash::new_unique().to_string(), + }, + incremental: SnapshotInfo { + slot: 8_200, + hash: Hash::new_unique().to_string(), + }, + } + ) + }, "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 cad2e475cb8a89..7169c477c40c61 100644 --- a/client/src/rpc_client.rs +++ b/client/src/rpc_client.rs @@ -1061,6 +1061,35 @@ impl RpcClient { self.send(RpcRequest::GetSnapshotSlot, Value::Null) } + /// Returns the snapshot information for the given snapshot slot and hash + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getSnapshotInfo`] RPC method. + /// + /// [`getSnapshotInfo`]: https://docs.solana.com/developing/clients/jsonrpc-api#getsnapshotinfo + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # rpc_client::RpcClient, + /// # client_error::ClientError, + /// # }; + /// # use solana_sdk::clock::Slot; + /// # use solana_sdk::hash::Hash; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// # let snapshot_slot_hash = (Slot::default(), Hash::default()); + /// let snapshot_info = rpc_client.get_snapshot_info(snapshot_slot_hash)?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub fn get_snapshot_info( + &self, + snapshot_slot_hash: (Slot, Hash), + ) -> ClientResult { + self.send(RpcRequest::GetSnapshotInfo, json!([snapshot_slot_hash])) + } + /// Check if a transaction has been processed with the default commitment level. /// /// If the transaction has been processed with the default commitment level, diff --git a/client/src/rpc_request.rs b/client/src/rpc_request.rs index eb3329e30d43c4..71f28c5c0c6eb2 100644 --- a/client/src/rpc_request.rs +++ b/client/src/rpc_request.rs @@ -85,6 +85,7 @@ pub enum RpcRequest { note = "Please use RpcRequest::GetHighestSnapshotSlot instead" )] GetSnapshotSlot, + GetSnapshotInfo, GetSignaturesForAddress, GetSignatureStatuses, GetSlot, @@ -158,6 +159,7 @@ impl fmt::Display for RpcRequest { RpcRequest::GetRecentPerformanceSamples => "getRecentPerformanceSamples", RpcRequest::GetHighestSnapshotSlot => "getHighestSnapshotSlot", RpcRequest::GetSnapshotSlot => "getSnapshotSlot", + RpcRequest::GetSnapshotInfo => "getSnapshotInfo", RpcRequest::GetSignaturesForAddress => "getSignaturesForAddress", RpcRequest::GetSignatureStatuses => "getSignatureStatuses", RpcRequest::GetSlot => "getSlot", diff --git a/client/src/rpc_response.rs b/client/src/rpc_response.rs index 77f4922ca8172d..7c6afc74573b8c 100644 --- a/client/src/rpc_response.rs +++ b/client/src/rpc_response.rs @@ -436,8 +436,23 @@ impl From for RpcConfirmedTransactionSt } } -#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] pub struct RpcSnapshotSlotInfo { pub full: Slot, pub incremental: Option, } + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub enum RpcSnapshotInfo { + FullSnapshot(SnapshotInfo), + IncrementalSnapshot { + full: SnapshotInfo, + incremental: SnapshotInfo, + }, +} + +#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq)] +pub struct SnapshotInfo { + pub slot: Slot, + pub hash: String, +} diff --git a/docs/src/developing/clients/jsonrpc-api.md b/docs/src/developing/clients/jsonrpc-api.md index 127fe896195f45..c6380176747a80 100644 --- a/docs/src/developing/clients/jsonrpc-api.md +++ b/docs/src/developing/clients/jsonrpc-api.md @@ -53,6 +53,7 @@ gives a convenient interface for the RPC methods. - [getSlot](jsonrpc-api.md#getslot) - [getSlotLeader](jsonrpc-api.md#getslotleader) - [getSlotLeaders](jsonrpc-api.md#getslotleaders) +- [getSnapshotInfo](jsonrpc-api.md#getsnapshotinfo) - [getStakeActivation](jsonrpc-api.md#getstakeactivation) - [getSupply](jsonrpc-api.md#getsupply) - [getTokenAccountBalance](jsonrpc-api.md#gettokenaccountbalance) @@ -2444,6 +2445,85 @@ The first leader returned is the leader for slot #100: } ``` +### getSnapshotInfo + +**NEW: This method is only available in solana-core v1.8 or newer.** + +Returns the snapshot information for the given snapshot slot and hash. + +If the node has a snapshot with the given slot and hash, it will return the +associated information about it. For full snapshots, this is the slot and has. +For incremental snapshots, this is the slot and hash _plus_ the slot and hash +for its assocaited full snapshot. + +#### Parameters: + +- `` + - `slot`: `` - The slot for the snapshot being queried + - `hash`: `` - A base-58 encoded hash for the snapshot being queried + +#### Results: + bprumo TODO: make correct + +- `` + - `full: ` - TODO + - `incremental: ` - TODO + +#### Example: + +Request: +```bash +curl http://localhost:8899 -X POST -H "Content-Type: application/json" -d ' + {"jsonrpc":"2.0","id":1,"method":"getSnapshotInfo","params":[{"slot":123,"hash":"CiDwVBFgWV9E5MvXWoLgnEgn2hK7rJikbvfWavzAQz3"}]} +' +``` + +Result when the node has a full snapshot for this slot and hash: +```json +{ + "jsonrpc": "2.0", + "result": { + "FullSnapshot": { + "slot": 123 + "hash": "CiDwVBFgWV9E5MvXWoLgnEgn2hK7rJikbvfWavzAQz3", + } + }, + "id": 1 +} +``` + +Result when the node has an incremental snapshot for this slot and hash: +```json +{ + "jsonrpc": "2.0", + "result": { + "IncrementalSnapshot": { + "full": { + "slot": 8000 + "hash": "4uQeVj5tqViQh7yWWGStvkEG1Zmhx6uasJtWCJziofM", + }, + "incremental": { + "slot": 8200 + "hash": "8opHzTAnfzRpPEx21XtnrVTX28YQuCpAjcn1PczScKh", + } + } + }, + "id": 1 +} +``` + +Result when the node does not have a snapshot for this slot and hash: +```json +{ + "jsonrpc": "2.0", + "error": { + "code": -32008, + "message": "No snapshot" + }, + "id": 1 +} +``` + ### getStakeActivation Returns epoch activation information for a stake account diff --git a/rpc/src/rpc.rs b/rpc/src/rpc.rs index 76af567f3b8a4f..147c8b95ec6d27 100644 --- a/rpc/src/rpc.rs +++ b/rpc/src/rpc.rs @@ -45,6 +45,7 @@ use { commitment::{BlockCommitmentArray, BlockCommitmentCache, CommitmentSlots}, inline_spl_token_v2_0::{SPL_TOKEN_ACCOUNT_MINT_OFFSET, SPL_TOKEN_ACCOUNT_OWNER_OFFSET}, non_circulating_supply::calculate_non_circulating_supply, + snapshot_archive_info::{IncrementalSnapshotArchiveInfo, SnapshotArchiveInfoGetter}, snapshot_config::SnapshotConfig, snapshot_utils, }, @@ -2275,6 +2276,13 @@ pub mod rpc_minimal { #[rpc(meta, name = "getHighestSnapshotSlot")] fn get_highest_snapshot_slot(&self, meta: Self::Metadata) -> Result; + #[rpc(meta, name = "getSnapshotInfo")] + fn get_snapshot_info( + &self, + meta: Self::Metadata, + snapshot_slot_hash: (Slot, Hash), + ) -> Result; + #[rpc(meta, name = "getTransactionCount")] fn get_transaction_count( &self, @@ -2396,6 +2404,81 @@ pub mod rpc_minimal { }) } + fn get_snapshot_info( + &self, + meta: Self::Metadata, + snapshot_slot_hash: (Slot, Hash), + ) -> Result { + debug!("get_snapshot_info rpc request received"); + + 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 incremental_snapshot_archives = + snapshot_utils::get_incremental_snapshot_archives(&snapshot_archives_dir); + let incremental_snapshot_archive = + incremental_snapshot_archives + .iter() + .find(|snapshot_archive| { + snapshot_archive.slot() == snapshot_slot_hash.0 + && snapshot_archive.hash() == &snapshot_slot_hash.1 + }); + + // If the snapshot_slot_hash passed in matched an incremental snapshot (above), then + // find its matching full snapshot. Otherwise, see if the snapshot_slot_hash matches a + // full snapshot. + // + // NOTE: Since IncrementalSnapshotArchiveInfo doesn't contain the full snapshot's hash + // (just the slot), we need two different calls to `.find()` to determine a match. + + let full_snapshot_archives = + snapshot_utils::get_full_snapshot_archives(&snapshot_archives_dir); + let full_snapshot_archive = match incremental_snapshot_archive { + Some(incremental_snapshot_archive) => { + full_snapshot_archives.iter().find(|full_snapshot_archive| { + full_snapshot_archive.slot() == incremental_snapshot_archive.base_slot() + }) + } + None => full_snapshot_archives.iter().find(|snapshot_archive| { + snapshot_archive.slot() == snapshot_slot_hash.0 + && snapshot_archive.hash() == &snapshot_slot_hash.1 + }), + }; + + // NOTE: If somehow an incremental snapshot was found but *not* its matching full + // snapshot, then something is wrong with the node (maybe someone deleted the full + // snapshot from the filesystem?), so just return a NoSnapshot error because this + // incremental snapshot is unusable on its own. + + match full_snapshot_archive { + None => Err(RpcCustomError::NoSnapshot.into()), + Some(full_snapshot_archive) => match incremental_snapshot_archive { + None => Ok(RpcSnapshotInfo::FullSnapshot(SnapshotInfo { + slot: full_snapshot_archive.slot(), + hash: full_snapshot_archive.hash().to_string(), + })), + Some(incremental_snapshot_archive) => { + Ok(RpcSnapshotInfo::IncrementalSnapshot { + full: SnapshotInfo { + slot: full_snapshot_archive.slot(), + hash: full_snapshot_archive.hash().to_string(), + }, + incremental: SnapshotInfo { + slot: incremental_snapshot_archive.slot(), + hash: incremental_snapshot_archive.hash().to_string(), + }, + }) + } + }, + } + } + fn get_transaction_count( &self, meta: Self::Metadata, diff --git a/runtime/src/snapshot_utils.rs b/runtime/src/snapshot_utils.rs index c2ce01d98b9c96..967dd7a834a8f3 100644 --- a/runtime/src/snapshot_utils.rs +++ b/runtime/src/snapshot_utils.rs @@ -1146,7 +1146,7 @@ where } /// Get a list of the incremental snapshot archives in a directory -fn get_incremental_snapshot_archives

( +pub fn get_incremental_snapshot_archives

( snapshot_archives_dir: P, ) -> Vec where