From 59fc33635a027f0bbddeca46f8bee05fd7fa96f5 Mon Sep 17 00:00:00 2001 From: Michael Vines Date: Mon, 26 Apr 2021 18:27:35 -0700 Subject: [PATCH] Add getVoteAccounts RPC method parameter to restrict results to a single vote account --- client/src/rpc_client.rs | 30 ++++++----- client/src/rpc_config.rs | 8 +++ core/src/rpc.rs | 61 ++++++++++++++++++---- docs/src/developing/clients/jsonrpc-api.md | 50 +++++++++++++++++- 4 files changed, 124 insertions(+), 25 deletions(-) diff --git a/client/src/rpc_client.rs b/client/src/rpc_client.rs index 86af2ba7704b84..0ef9d87d774087 100644 --- a/client/src/rpc_client.rs +++ b/client/src/rpc_client.rs @@ -496,10 +496,17 @@ impl RpcClient { &self, commitment_config: CommitmentConfig, ) -> ClientResult { - self.send( - RpcRequest::GetVoteAccounts, - json!([self.maybe_map_commitment(commitment_config)?]), - ) + self.get_vote_accounts_with_config(RpcGetVoteAccountsConfig { + commitment: Some(self.maybe_map_commitment(commitment_config)?), + ..RpcGetVoteAccountsConfig::default() + }) + } + + pub fn get_vote_accounts_with_config( + &self, + config: RpcGetVoteAccountsConfig, + ) -> ClientResult { + self.send(RpcRequest::GetVoteAccounts, json!([config])) } pub fn wait_for_max_stake( @@ -884,15 +891,12 @@ impl RpcClient { slot: Option, commitment_config: CommitmentConfig, ) -> ClientResult> { - self.send( - RpcRequest::GetLeaderSchedule, - json!([ - slot, - RpcLeaderScheduleConfig { - commitment: Some(self.maybe_map_commitment(commitment_config)?), - ..RpcLeaderScheduleConfig::default() - } - ]), + self.get_leader_schedule_with_config( + slot, + RpcLeaderScheduleConfig { + commitment: Some(self.maybe_map_commitment(commitment_config)?), + ..RpcLeaderScheduleConfig::default() + }, ) } diff --git a/client/src/rpc_config.rs b/client/src/rpc_config.rs index b3e3d7366d595f..5faedc06e6fdba 100644 --- a/client/src/rpc_config.rs +++ b/client/src/rpc_config.rs @@ -49,6 +49,14 @@ pub struct RpcLeaderScheduleConfig { pub commitment: Option, } +#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RpcGetVoteAccountsConfig { + pub vote_pubkey: Option, // validator vote address, as a base-58 encoded string + #[serde(flatten)] + pub commitment: Option, +} + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(untagged)] pub enum RpcLeaderScheduleConfigWrapper { diff --git a/core/src/rpc.rs b/core/src/rpc.rs index 831efae49faeb4..f7912f64612277 100644 --- a/core/src/rpc.rs +++ b/core/src/rpc.rs @@ -702,9 +702,17 @@ impl JsonRpcRequestProcessor { fn get_vote_accounts( &self, - commitment: Option, + config: Option, ) -> Result { - let bank = self.bank(commitment); + let config = config.unwrap_or_default(); + + let filter_by_vote_pubkey = if let Some(ref vote_pubkey) = config.vote_pubkey { + Some(verify_pubkey(vote_pubkey)?) + } else { + None + }; + + let bank = self.bank(config.commitment); let vote_accounts = bank.vote_accounts(); let epoch_vote_accounts = bank .epoch_vote_accounts(bank.get_epoch_and_slot_index(bank.slot()).0) @@ -715,7 +723,13 @@ impl JsonRpcRequestProcessor { Vec, ) = vote_accounts .iter() - .map(|(pubkey, (activated_stake, account))| { + .filter_map(|(vote_pubkey, (activated_stake, account))| { + if let Some(filter_by_vote_pubkey) = filter_by_vote_pubkey { + if *vote_pubkey != filter_by_vote_pubkey { + return None; + } + } + let vote_state = account.vote_state(); let vote_state = vote_state.as_ref().unwrap_or(&default_vote_state); let last_vote = if let Some(vote) = vote_state.votes.iter().last() { @@ -735,16 +749,16 @@ impl JsonRpcRequestProcessor { epoch_credits.clone() }; - RpcVoteAccountInfo { - vote_pubkey: (pubkey).to_string(), + Some(RpcVoteAccountInfo { + vote_pubkey: vote_pubkey.to_string(), node_pubkey: vote_state.node_pubkey.to_string(), activated_stake: *activated_stake, commission: vote_state.commission, root_slot: vote_state.root_slot.unwrap_or(0), epoch_credits, - epoch_vote_account: epoch_vote_accounts.contains_key(pubkey), + epoch_vote_account: epoch_vote_accounts.contains_key(vote_pubkey), last_vote, - } + }) }) .partition(|vote_account_info| { if bank.slot() >= DELINQUENT_VALIDATOR_SLOT_DISTANCE as u64 { @@ -2100,7 +2114,7 @@ pub mod rpc_minimal { fn get_vote_accounts( &self, meta: Self::Metadata, - commitment: Option, + config: Option, ) -> Result; // TODO: Refactor `solana-validator wait-for-restart-window` to not require this method, so @@ -2203,10 +2217,10 @@ pub mod rpc_minimal { fn get_vote_accounts( &self, meta: Self::Metadata, - commitment: Option, + config: Option, ) -> Result { debug!("get_vote_accounts rpc request received"); - meta.get_vote_accounts(commitment) + meta.get_vote_accounts(config) } // TODO: Refactor `solana-validator wait-for-restart-window` to not require this method, so @@ -6046,6 +6060,33 @@ pub mod tests { ] ); + // Filter request based on the leader: + { + let req = format!( + r#"{{"jsonrpc":"2.0","id":1,"method":"getVoteAccounts","params":{}}}"#, + json!([RpcGetVoteAccountsConfig { + vote_pubkey: Some(leader_vote_keypair.pubkey().to_string()), + commitment: Some(CommitmentConfig::processed()) + }]) + ); + + let res = io.handle_request_sync(&req, meta.clone()); + let result: Value = serde_json::from_str(&res.expect("actual response")) + .expect("actual response deserialization"); + + let vote_account_status: RpcVoteAccountStatus = + serde_json::from_value(result["result"].clone()).unwrap(); + + assert_eq!(vote_account_status.current.len(), 1); + assert_eq!(vote_account_status.delinquent.len(), 0); + for vote_account_info in vote_account_status.current { + assert_eq!( + vote_account_info.vote_pubkey, + leader_vote_keypair.pubkey().to_string() + ); + } + } + // Overflow the epoch credits history and ensure only `MAX_RPC_EPOCH_CREDITS_HISTORY` // results are returned for _ in 0..(TEST_SLOTS_PER_EPOCH * (MAX_RPC_EPOCH_CREDITS_HISTORY) as u64) { diff --git a/docs/src/developing/clients/jsonrpc-api.md b/docs/src/developing/clients/jsonrpc-api.md index 626a49c16d49ad..521fe653b2fb3d 100644 --- a/docs/src/developing/clients/jsonrpc-api.md +++ b/docs/src/developing/clients/jsonrpc-api.md @@ -2848,11 +2848,14 @@ Returns the account info and associated stake for all the voting accounts in the #### Parameters: -- `` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment) +- `` - (optional) Configuration object containing the following field: + - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment) + - (optional) `votePubkey: ` - Only return results for this validator vote address (base-58 encoded) #### Results: -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: +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 address, as base-58 encoded string - `nodePubkey: ` - Validator identity, as base-58 encoded string @@ -2905,6 +2908,49 @@ Result: } ``` +#### Example: Restrict results to a single validator vote account + +Request: +```bash +curl http://localhost:8899 -X POST -H "Content-Type: application/json" -d ' + { + "jsonrpc": "2.0", + "id": 1, + "method": "getVoteAccounts", + "params": [ + { + "votePubkey": "3ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw" + } + ] + } +' +``` + +Result: +```json +{ + "jsonrpc": "2.0", + "result": { + "current": [ + { + "commission": 0, + "epochVoteAccount": true, + "epochCredits": [ + [ 1, 64, 0 ], + [ 2, 192, 64 ] + ], + "nodePubkey": "B97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD", + "lastVote": 147, + "activatedStake": 42, + "votePubkey": "3ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw" + } + ], + "delinquent": [] + }, + "id": 1 +} +``` + ### minimumLedgerSlot Returns the lowest slot that the node has information about in its ledger. This