Add Incremental Snapshot support to RPC (#19559)

#### Problem

There's no way to get incremental snapshot information from RPC.

#### Summary of Changes

- Add new RPC method, `getHighestSnapshotSlot` that returns a `SnapshotSlotInfo`, which contains both the highest full snapshot slot, and the highest incremental snapshot slot _based on_ the full snapshot.
- Deprecate old RPC method, `getSnapshotSlot`
- Update API docs

Fixes #19579
This commit is contained in:
Brooks Prumo 2021-09-02 15:25:42 -05:00 committed by GitHub
parent f4f14c42bb
commit 8ac94b2cf4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 227 additions and 78 deletions

View File

@ -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" => {

View File

@ -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<RpcSnapshotSlotInfo> {
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<Slot> {
self.send(RpcRequest::GetSnapshotSlot, Value::Null)
}

View File

@ -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",

View File

@ -435,3 +435,9 @@ impl From<ConfirmedTransactionStatusWithSignature> for RpcConfirmedTransactionSt
}
}
}
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
pub struct RpcSnapshotSlotInfo {
pub full: Slot,
pub incremental: Option<Slot>,
}

View File

@ -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:
- `<object>`
- `full: <u64>` - Highest full snapshot slot
- `incremental: <u64 | undefined>` - 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:
- `<u64>` - 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:
- `<u64>` - 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}
```

View File

@ -2276,8 +2276,8 @@ pub mod rpc_minimal {
commitment: Option<CommitmentConfig>,
) -> Result<u64>;
#[rpc(meta, name = "getSnapshotSlot")]
fn get_snapshot_slot(&self, meta: Self::Metadata) -> Result<Slot>;
#[rpc(meta, name = "getHighestSnapshotSlot")]
fn get_highest_snapshot_slot(&self, meta: Self::Metadata) -> Result<RpcSnapshotSlotInfo>;
#[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<Slot> {
debug!("get_snapshot_slot rpc request received");
fn get_highest_snapshot_slot(&self, meta: Self::Metadata) -> Result<RpcSnapshotSlotInfo> {
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<RpcResponse<RpcFeeRateGovernor>>;
#[rpc(meta, name = "getSnapshotSlot")]
fn get_snapshot_slot(&self, meta: Self::Metadata) -> Result<Slot>;
}
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<Slot> {
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())
}
}
}

View File

@ -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<SnapshotConfig>,
bank_forks: Arc<RwLock<BankForks>>,
health: Arc<RpcHealth>,
@ -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(".."));

View File

@ -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
));

View File

@ -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.,