From 8ffd2c12a38681aac03bd2d57c915bb67663e24d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 21 Jan 2020 14:07:32 -0800 Subject: [PATCH] Add and use minimumLedgerSlot RPC API in block-production command (bp #7901) (#7903) automerge --- book/src/api-reference/jsonrpc-api.md | 28 +++++++++++++++++++-- cli/src/cluster_query.rs | 28 ++++++++++++++++++--- client/src/rpc_client.rs | 19 +++++++++++++++ client/src/rpc_request.rs | 2 ++ core/src/rpc.rs | 35 +++++++++++++++++++++++++++ 5 files changed, 107 insertions(+), 5 deletions(-) diff --git a/book/src/api-reference/jsonrpc-api.md b/book/src/api-reference/jsonrpc-api.md index 0667659ae6..7bbdb44c53 100644 --- a/book/src/api-reference/jsonrpc-api.md +++ b/book/src/api-reference/jsonrpc-api.md @@ -40,6 +40,7 @@ To interact with a Solana node inside a JavaScript application, use the [solana- * [getTotalSupply](jsonrpc-api.md#gettotalsupply) * [getVersion](jsonrpc-api.md#getversion) * [getVoteAccounts](jsonrpc-api.md#getvoteaccounts) +* [minimumLedgerSlot](jsonrpc-api.md#minimumledgerslot) * [requestAirdrop](jsonrpc-api.md#requestairdrop) * [sendTransaction](jsonrpc-api.md#sendtransaction) * [startSubscriptionChannel](jsonrpc-api.md#startsubscriptionchannel) @@ -585,7 +586,7 @@ Returns the current slot the node is processing curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getSlot"}' http://localhost:8899 // Result -{"jsonrpc":"2.0","result":"1234","id":1} +{"jsonrpc":"2.0","result":1234,"id":1} ``` ### getSlotLeader @@ -628,7 +629,7 @@ Returns the current storage segment size in terms of slots // Request curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getSlotsPerSegment"}' http://localhost:8899 // Result -{"jsonrpc":"2.0","result":"1024","id":1} +{"jsonrpc":"2.0","result":1024,"id":1} ``` ### getStorageTurn @@ -772,6 +773,29 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "m {"jsonrpc":"2.0","result":{"current":[{"commission":0,"epochVoteAccount":true,"nodePubkey":"B97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD","lastVote":147,"activatedStake":42,"votePubkey":"3ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw"}],"delinquent":[{"commission":127,"epochVoteAccount":false,"nodePubkey":"6ZPxeQaDo4bkZLRsdNrCzchNQr5LN9QMc9sipXv9Kw8f","lastVote":0,"activatedStake":0,"votePubkey":"CmgCk4aMS7KW1SHX3s9K5tBJ6Yng2LBaC8MFov4wx9sm"}]},"id":1} ``` +### minimumLedgerSlot + +Returns the lowest slot that the node has information about in its ledger. This +value may increase over time if the node is configured to purge older ledger data + +#### Parameters: + +None + +#### Results: + +* `u64` - Minimum ledger slot + +#### Example: + +```bash +// Request +curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"minimumLedgerSlot"}' http://localhost:8899 + +// Result +{"jsonrpc":"2.0","result":1234,"id":1} +``` + ### requestAirdrop Requests an airdrop of lamports to a Pubkey diff --git a/cli/src/cluster_query.rs b/cli/src/cluster_query.rs index dd9e7bb5af..873e52172d 100644 --- a/cli/src/cluster_query.rs +++ b/cli/src/cluster_query.rs @@ -434,19 +434,39 @@ pub fn process_show_block_production( return Err(format!("Epoch {} is in the future", epoch).into()); } + let minimum_ledger_slot = rpc_client.minimum_ledger_slot()?; + let first_slot_in_epoch = epoch_schedule.get_first_slot_in_epoch(epoch); let end_slot = std::cmp::min( epoch_info.absolute_slot, epoch_schedule.get_last_slot_in_epoch(epoch), ); - let start_slot = if let Some(slot_limit) = slot_limit { + let mut start_slot = if let Some(slot_limit) = slot_limit { std::cmp::max(end_slot.saturating_sub(slot_limit), first_slot_in_epoch) } else { first_slot_in_epoch }; - let start_slot_index = (start_slot - first_slot_in_epoch) as usize; - let end_slot_index = (end_slot - first_slot_in_epoch) as usize; + + if minimum_ledger_slot > end_slot { + return Err(format!( + "Ledger data not available for slots {} to {} (minimum ledger slot is {})", + start_slot, end_slot, minimum_ledger_slot + ) + .into()); + } + + if minimum_ledger_slot > start_slot { + println!( + "\n{}", + style(format!( + "Note: Requested start slot was {} but minimum ledger slot is {}", + start_slot, minimum_ledger_slot + )) + .italic(), + ); + start_slot = minimum_ledger_slot; + } let progress_bar = new_spinner_progress_bar(); progress_bar.set_message(&format!( @@ -455,6 +475,8 @@ pub fn process_show_block_production( )); let confirmed_blocks = rpc_client.get_confirmed_blocks(start_slot, Some(end_slot))?; + let start_slot_index = (start_slot - first_slot_in_epoch) as usize; + let end_slot_index = (end_slot - first_slot_in_epoch) as usize; let total_slots = end_slot_index - start_slot_index + 1; let total_blocks = confirmed_blocks.len(); assert!(total_blocks <= total_slots); diff --git a/client/src/rpc_client.rs b/client/src/rpc_client.rs index 38ec7d68c6..eacfb51954 100644 --- a/client/src/rpc_client.rs +++ b/client/src/rpc_client.rs @@ -386,6 +386,25 @@ impl RpcClient { }) } + pub fn minimum_ledger_slot(&self) -> io::Result { + let response = self + .client + .send(&RpcRequest::MinimumLedgerSlot, Value::Null, 0) + .map_err(|err| { + io::Error::new( + io::ErrorKind::Other, + format!("MinimumLedgerSlot request failure: {:?}", err), + ) + })?; + + serde_json::from_value(response).map_err(|err| { + io::Error::new( + io::ErrorKind::Other, + format!("MinimumLedgerSlot parse failure: {}", err), + ) + }) + } + pub fn send_and_confirm_transaction( &self, transaction: &mut Transaction, diff --git a/client/src/rpc_request.rs b/client/src/rpc_request.rs index e2208ad383..be0b539526 100644 --- a/client/src/rpc_request.rs +++ b/client/src/rpc_request.rs @@ -35,6 +35,7 @@ pub enum RpcRequest { SendTransaction, SignVote, GetMinimumBalanceForRentExemption, + MinimumLedgerSlot, } impl RpcRequest { @@ -75,6 +76,7 @@ impl RpcRequest { RpcRequest::SendTransaction => "sendTransaction", RpcRequest::SignVote => "signVote", RpcRequest::GetMinimumBalanceForRentExemption => "getMinimumBalanceForRentExemption", + RpcRequest::MinimumLedgerSlot => "minimumLedgerSlot", }; json!({ "jsonrpc": jsonrpc, diff --git a/core/src/rpc.rs b/core/src/rpc.rs index 6f3e21bdf7..1e73927ff9 100644 --- a/core/src/rpc.rs +++ b/core/src/rpc.rs @@ -229,6 +229,19 @@ impl JsonRpcRequestProcessor { Ok(self.bank(commitment).collector_id().to_string()) } + fn minimum_ledger_slot(&self) -> Result { + match self.blockstore.slot_meta_iterator(0) { + Ok(mut metas) => match metas.next() { + Some((slot, _meta)) => Ok(slot), + None => Err(Error::invalid_request()), + }, + Err(err) => { + warn!("slot_meta_iterator failed: {:?}", err); + Err(Error::invalid_request()) + } + } + } + fn get_transaction_count(&self, commitment: Option) -> Result { Ok(self.bank(commitment).transaction_count() as u64) } @@ -530,6 +543,9 @@ pub trait RpcSol { commitment: Option, ) -> Result; + #[rpc(meta, name = "minimumLedgerSlot")] + fn minimum_ledger_slot(&self, meta: Self::Metadata) -> Result; + #[rpc(meta, name = "getVoteAccounts")] fn get_vote_accounts( &self, @@ -990,6 +1006,10 @@ impl RpcSol for RpcSolImpl { .get_slot_leader(commitment) } + fn minimum_ledger_slot(&self, meta: Self::Metadata) -> Result { + meta.request_processor.read().unwrap().minimum_ledger_slot() + } + fn get_vote_accounts( &self, meta: Self::Metadata, @@ -1379,6 +1399,21 @@ pub mod tests { assert_eq!(expected, result); } + #[test] + fn test_rpc_minimum_ledger_slot() { + let bob_pubkey = Pubkey::new_rand(); + let RpcHandler { io, meta, .. } = start_rpc_handler_with_tx(&bob_pubkey); + + let req = format!(r#"{{"jsonrpc":"2.0","id":1,"method":"minimumLedgerSlot"}}"#); + let res = io.handle_request_sync(&req, meta); + let expected = r#"{"jsonrpc":"2.0","result":0,"id":1}"#; + let expected: Response = + serde_json::from_str(&expected).expect("expected response deserialization"); + let result: Response = serde_json::from_str(&res.expect("actual response")) + .expect("actual response deserialization"); + assert_eq!(expected, result); + } + #[test] fn test_rpc_get_total_supply() { let bob_pubkey = Pubkey::new_rand();