diff --git a/client/src/rpc_client.rs b/client/src/rpc_client.rs index f45acc3222..ed42ecbf0c 100644 --- a/client/src/rpc_client.rs +++ b/client/src/rpc_client.rs @@ -4,12 +4,7 @@ use { http_sender::HttpSender, mock_sender::{MockSender, Mocks}, rpc_config::RpcAccountInfoConfig, - rpc_config::{ - RpcConfirmedBlockConfig, RpcConfirmedTransactionConfig, RpcEpochConfig, - RpcGetConfirmedSignaturesForAddress2Config, RpcLargestAccountsConfig, - RpcProgramAccountsConfig, RpcRequestAirdropConfig, RpcSendTransactionConfig, - RpcSimulateTransactionConfig, RpcTokenAccountsFilter, - }, + rpc_config::*, rpc_request::{RpcError, RpcRequest, RpcResponseErrorData, TokenAccountsFilter}, rpc_response::*, rpc_sender::RpcSender, @@ -732,7 +727,7 @@ impl RpcClient { &self, slot: Option, ) -> ClientResult> { - self.get_leader_schedule_with_commitment(slot, self.commitment_config) + self.get_leader_schedule_with_config(slot, RpcLeaderScheduleConfig::default()) } pub fn get_leader_schedule_with_commitment( @@ -742,10 +737,24 @@ impl RpcClient { ) -> ClientResult> { self.send( RpcRequest::GetLeaderSchedule, - json!([slot, self.maybe_map_commitment(commitment_config)?]), + json!([ + slot, + RpcLeaderScheduleConfig { + commitment: Some(self.maybe_map_commitment(commitment_config)?), + ..RpcLeaderScheduleConfig::default() + } + ]), ) } + pub fn get_leader_schedule_with_config( + &self, + slot: Option, + config: RpcLeaderScheduleConfig, + ) -> ClientResult> { + self.send(RpcRequest::GetLeaderSchedule, json!([slot, config])) + } + pub fn get_epoch_schedule(&self) -> ClientResult { self.send(RpcRequest::GetEpochSchedule, Value::Null) } diff --git a/client/src/rpc_config.rs b/client/src/rpc_config.rs index fbd8ad0839..eb67da9cc5 100644 --- a/client/src/rpc_config.rs +++ b/client/src/rpc_config.rs @@ -41,6 +41,14 @@ pub struct RpcRequestAirdropConfig { pub commitment: Option, } +#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RpcLeaderScheduleConfig { + pub identity: Option, // validator identity, as a base-58 encoded string + #[serde(flatten)] + pub commitment: Option, +} + #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum RpcLargestAccountsFilter { diff --git a/client/src/rpc_response.rs b/client/src/rpc_response.rs index e92137ba22..3af12c4ed3 100644 --- a/client/src/rpc_response.rs +++ b/client/src/rpc_response.rs @@ -254,10 +254,10 @@ pub struct RpcVoteAccountStatus { #[derive(Serialize, Deserialize, Clone, Debug)] #[serde(rename_all = "camelCase")] pub struct RpcVoteAccountInfo { - /// Vote account pubkey as base-58 encoded string + /// Vote account address, as base-58 encoded string pub vote_pubkey: String, - /// The pubkey of the node that votes using this account + /// The validator identity, as base-58 encoded string pub node_pubkey: String, /// The current stake, in lamports, delegated to this vote account diff --git a/core/src/rpc.rs b/core/src/rpc.rs index b5a0ab0f0f..c19e163e0e 100644 --- a/core/src/rpc.rs +++ b/core/src/rpc.rs @@ -2109,7 +2109,7 @@ pub mod rpc_minimal { &self, meta: Self::Metadata, slot: Option, - commitment: Option, + config: Option, ) -> Result>; } @@ -2214,9 +2214,15 @@ pub mod rpc_minimal { &self, meta: Self::Metadata, slot: Option, - commitment: Option, + config: Option, ) -> Result> { - let bank = meta.bank(commitment); + let config = config.unwrap_or_default(); + + if let Some(ref identity) = config.identity { + let _ = verify_pubkey(identity)?; + } + + let bank = meta.bank(config.commitment); let slot = slot.unwrap_or_else(|| bank.slot()); let epoch = bank.epoch_schedule().get_epoch(slot); @@ -2226,9 +2232,14 @@ pub mod rpc_minimal { .leader_schedule_cache .get_epoch_leader_schedule(epoch) .map(|leader_schedule| { - solana_ledger::leader_schedule_utils::leader_schedule_by_identity( - leader_schedule.get_slot_leaders().iter().enumerate(), - ) + let mut schedule_by_identity = + solana_ledger::leader_schedule_utils::leader_schedule_by_identity( + leader_schedule.get_slot_leaders().iter().enumerate(), + ); + if let Some(identity) = config.identity { + schedule_by_identity.retain(|k, _| *k == identity); + } + schedule_by_identity })) } } @@ -4107,6 +4118,10 @@ pub mod tests { for req in [ r#"{"jsonrpc":"2.0","id":1,"method":"getLeaderSchedule", "params": [0]}"#, r#"{"jsonrpc":"2.0","id":1,"method":"getLeaderSchedule"}"#, + &format!( + r#"{{"jsonrpc":"2.0","id":1,"method":"getLeaderSchedule", "params": [null, {{ "identity": "{}" }}]}}"#, + bank.collector_id().to_string() + ), ] .iter() { @@ -4139,7 +4154,7 @@ pub mod tests { } let req = r#"{"jsonrpc":"2.0","id":1,"method":"getLeaderSchedule", "params": [42424242]}"#; - let rep = io.handle_request_sync(&req, meta); + let rep = io.handle_request_sync(&req, meta.clone()); let res: Response = serde_json::from_str(&rep.expect("actual response")) .expect("actual response deserialization"); @@ -4153,6 +4168,27 @@ pub mod tests { panic!("Expected single response"); }; assert_eq!(schedule, None); + + // `bob` is not in the leader schedule, look for an empty response + let req = format!( + r#"{{"jsonrpc":"2.0","id":1,"method":"getLeaderSchedule", "params": [null, {{ "identity": "{}"}}]}}"#, + bob_pubkey + ); + + let rep = io.handle_request_sync(&req, meta); + let res: Response = serde_json::from_str(&rep.expect("actual response")) + .expect("actual response deserialization"); + + let schedule: Option = if let Response::Single(res) = res { + if let Output::Success(res) = res { + serde_json::from_value(res.result).unwrap() + } else { + panic!("Expected success"); + } + } else { + panic!("Expected single response"); + }; + assert_eq!(schedule, Some(HashMap::default())); } #[test] diff --git a/docs/src/developing/clients/jsonrpc-api.md b/docs/src/developing/clients/jsonrpc-api.md index c3b67cc7ef..3a87e31f47 100644 --- a/docs/src/developing/clients/jsonrpc-api.md +++ b/docs/src/developing/clients/jsonrpc-api.md @@ -1641,14 +1641,17 @@ Returns the leader schedule for an epoch #### Parameters: -- `` - (optional) Fetch the leader schedule for the epoch that corresponds to the provided slot. If unspecified, the leader schedule for the current epoch is fetched -- `` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment) +- `` - (optional) Fetch the leader schedule for the epoch that corresponds to the provided slot. + If unspecified, the leader schedule for the current epoch is fetched +- `` - (optional) Configuration object containing the following field: + - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment) + - (optional) `identity: ` - Only return results for this validator identity (base-58 encoded) #### Results: - `` - if requested epoch is not found -- `` - otherwise, the result field will be a dictionary of leader public keys - \(as base-58 encoded strings\) and their corresponding leader slot indices as values +- `` - otherwise, the result field will be a dictionary of validator identities, + as base-58 encoded strings, and their corresponding leader slot indices as values (indices are relative to the first slot in the requested epoch) #### Example: @@ -1671,6 +1674,36 @@ Result: } ``` +#### Example: + +Request: +```bash +curl http://localhost:8899 -X POST -H "Content-Type: application/json" -d ' + { + "jsonrpc": "2.0", + "id": 1, + "method": "getLeaderSchedule", + "params": [ + null, + { + "identity": "4Qkev8aNZcqFNSRhQzwyLMFSsi94jHqE8WNVTJzTP99F" + } + ] + } +' +``` + +Result: +```json +{ + "jsonrpc":"2.0", + "result":{ + "4Qkev8aNZcqFNSRhQzwyLMFSsi94jHqE8WNVTJzTP99F":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63] + }, + "id":1 +} +``` + ### getMaxRetransmitSlot Get the max slot seen from retransmit stage. @@ -2865,8 +2898,8 @@ Returns the account info and associated stake for all the voting accounts in the The result field will be a JSON object of `current` and `delinquent` accounts, each containing an array of JSON objects with the following sub fields: -- `votePubkey: ` - Vote account public key, as base-58 encoded string -- `nodePubkey: ` - Node public key, as base-58 encoded string +- `votePubkey: ` - Vote account address, as base-58 encoded string +- `nodePubkey: ` - Validator identity, as base-58 encoded string - `activatedStake: ` - the stake, in lamports, delegated to this vote account and active in this epoch - `epochVoteAccount: ` - bool, whether the vote account is staked for this epoch - `commission: `, percentage (0-100) of rewards payout owed to the vote account diff --git a/validator/src/main.rs b/validator/src/main.rs index 383945e7bd..2046525353 100644 --- a/validator/src/main.rs +++ b/validator/src/main.rs @@ -16,7 +16,10 @@ use { }, keypair::SKIP_SEED_PHRASE_VALIDATION_ARG, }, - solana_client::{rpc_client::RpcClient, rpc_request::MAX_MULTIPLE_ACCOUNTS}, + solana_client::{ + rpc_client::RpcClient, rpc_config::RpcLeaderScheduleConfig, + rpc_request::MAX_MULTIPLE_ACCOUNTS, + }, solana_core::ledger_cleanup_service::{ DEFAULT_MAX_LEDGER_SHREDS, DEFAULT_MIN_MAX_LEDGER_SHREDS, }, @@ -154,7 +157,13 @@ fn wait_for_restart_window( )); let first_slot_in_epoch = epoch_info.absolute_slot - epoch_info.slot_index; leader_schedule = rpc_client - .get_leader_schedule(Some(first_slot_in_epoch))? + .get_leader_schedule_with_config( + Some(first_slot_in_epoch), + RpcLeaderScheduleConfig { + identity: Some(identity.to_string()), + ..RpcLeaderScheduleConfig::default() + }, + )? .ok_or_else(|| { format!( "Unable to get leader schedule from slot {}",