diff --git a/book/src/jsonrpc-api.md b/book/src/jsonrpc-api.md index dd49e4032d..84cefb589b 100644 --- a/book/src/jsonrpc-api.md +++ b/book/src/jsonrpc-api.md @@ -30,6 +30,7 @@ Methods * [getSlotLeader](#getslotleader) * [getNumBlocksSinceSignatureConfirmation](#getnumblockssincesignatureconfirmation) * [getTransactionCount](#gettransactioncount) +* [getEpochVoteAccounts](#getepochvoteaccounts) * [requestAirdrop](#requestairdrop) * [sendTransaction](#sendtransaction) * [startSubscriptionChannel](#startsubscriptionchannel) @@ -274,6 +275,39 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "m --- +### getEpochVoteAccounts +Returns the account info and associated stake for all the voting accounts in the current epoch. + +##### Parameters: +None + +##### Results: +An array consisting of vote accounts: +* `string` - the vote account's Pubkey as base-58 encoded string +* `integer` - the stake, in lamports, delegated to this vote account +* `VoteState` - the vote account's state + +Each VoteState will be a JSON object with the following sub fields: + +* `votes`, array of most recent vote lockouts +* `node_id`, the pubkey of the node that votes using this account +* `authorized_voter_id`, the pubkey of the authorized vote signer for this account +* `commission`, a 32-bit integer used as a fraction (commission/MAX_U32) for rewards payout +* `root_slot`, the most recent slot this account has achieved maximum lockout +* `credits`, credits accrued by this account for reaching lockouts + +##### Example: +```bash +// Request +curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getEpochVoteAccounts"}' http://localhost:8899 + +// Result +{"jsonrpc":"2.0","result":[[[84,115,89,23,41,83,221,72,58,23,53,245,195,188,140,161,242,189,200,164,139,214,12,180,84,161,28,151,24,243,159,125],10000000,{"authorized_voter_id":[84,115,89,23,41,83,221,72,58,23,53,245,195,188,140,161,242,189,200,164,139,214,12,180,84,161,28,151,24,243,159,125],"commission":0,"credits":0,"node_id":[49,139,227,211,47,39,69,86,131,244,160,144,228,169,84,143,142,253,83,81,212,110,254,12,242,71,219,135,30,60,157,213],"root_slot":null,"votes":[{"confirmation_count":1,"slot":0}]}]],"id":1} +``` + +--- + + ### requestAirdrop Requests an airdrop of lamports to a Pubkey diff --git a/client/src/rpc_request.rs b/client/src/rpc_request.rs index 730016a6fc..0c9850927a 100644 --- a/client/src/rpc_request.rs +++ b/client/src/rpc_request.rs @@ -13,6 +13,7 @@ pub enum RpcRequest { GetRecentBlockhash, GetSignatureStatus, GetSlotLeader, + GetEpochVoteAccounts, GetStorageBlockhash, GetStorageSlot, GetStoragePubkeysForSlot, @@ -39,6 +40,7 @@ impl RpcRequest { RpcRequest::GetRecentBlockhash => "getRecentBlockhash", RpcRequest::GetSignatureStatus => "getSignatureStatus", RpcRequest::GetSlotLeader => "getSlotLeader", + RpcRequest::GetEpochVoteAccounts => "getEpochVoteAccounts", RpcRequest::GetStorageBlockhash => "getStorageBlockhash", RpcRequest::GetStorageSlot => "getStorageSlot", RpcRequest::GetStoragePubkeysForSlot => "getStoragePubkeysForSlot", diff --git a/core/src/rpc.rs b/core/src/rpc.rs index de50ed969b..9aedb71498 100644 --- a/core/src/rpc.rs +++ b/core/src/rpc.rs @@ -6,7 +6,6 @@ use crate::contact_info::ContactInfo; use crate::packet::PACKET_DATA_SIZE; use crate::storage_stage::StorageState; use bincode::{deserialize, serialize}; -use bs58; use jsonrpc_core::{Error, Metadata, Result}; use jsonrpc_derive::rpc; use solana_drone::drone::request_airdrop_transaction; @@ -16,7 +15,7 @@ use solana_sdk::fee_calculator::FeeCalculator; use solana_sdk::pubkey::Pubkey; use solana_sdk::signature::Signature; use solana_sdk::transaction::{self, Transaction}; -use std::mem; +use solana_vote_api::vote_state::VoteState; use std::net::{SocketAddr, UdpSocket}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, RwLock}; @@ -76,9 +75,8 @@ impl JsonRpcRequestProcessor { } fn get_recent_blockhash(&self) -> (String, FeeCalculator) { - let id = self.bank().confirmed_last_blockhash(); ( - bs58::encode(id).into_string(), + self.bank().confirmed_last_blockhash().to_string(), self.bank().fee_calculator.clone(), ) } @@ -104,14 +102,22 @@ impl JsonRpcRequestProcessor { Ok(self.bank().transaction_count() as u64) } + fn get_epoch_vote_accounts(&self) -> Result> { + let bank = self.bank(); + Ok(bank + .epoch_vote_accounts(bank.get_stakers_epoch(bank.slot())) + .ok_or_else(Error::invalid_request)? + .iter() + .map(|(k, (s, a))| (*k, *s, VoteState::from(a).unwrap_or_default())) + .collect::>()) + } + fn get_storage_blockhash(&self) -> Result { - let hash = self.storage_state.get_storage_blockhash(); - Ok(bs58::encode(hash).into_string()) + Ok(self.storage_state.get_storage_blockhash().to_string()) } fn get_storage_slot(&self) -> Result { - let slot = self.storage_state.get_slot(); - Ok(slot) + Ok(self.storage_state.get_slot()) } fn get_storage_pubkeys_for_slot(&self, slot: u64) -> Result> { @@ -136,35 +142,11 @@ fn get_tpu_addr(cluster_info: &Arc>) -> Result { } fn verify_pubkey(input: String) -> Result { - let pubkey_vec = bs58::decode(input).into_vec().map_err(|err| { - info!("verify_pubkey: invalid input: {:?}", err); - Error::invalid_request() - })?; - if pubkey_vec.len() != mem::size_of::() { - info!( - "verify_pubkey: invalid pubkey_vec length: {}", - pubkey_vec.len() - ); - Err(Error::invalid_request()) - } else { - Ok(Pubkey::new(&pubkey_vec)) - } + input.parse().map_err(|_e| Error::invalid_request()) } fn verify_signature(input: &str) -> Result { - let signature_vec = bs58::decode(input).into_vec().map_err(|err| { - info!("verify_signature: invalid input: {}: {:?}", input, err); - Error::invalid_request() - })?; - if signature_vec.len() != mem::size_of::() { - info!( - "verify_signature: invalid signature_vec length: {}", - signature_vec.len() - ); - Err(Error::invalid_request()) - } else { - Ok(Signature::new(&signature_vec)) - } + input.parse().map_err(|_e| Error::invalid_request()) } #[derive(Clone)] @@ -224,6 +206,9 @@ pub trait RpcSol { #[rpc(meta, name = "getSlotLeader")] fn get_slot_leader(&self, _: Self::Metadata) -> Result; + #[rpc(meta, name = "getEpochVoteAccounts")] + fn get_epoch_vote_accounts(&self, _: Self::Metadata) -> Result>; + #[rpc(meta, name = "getStorageBlockhash")] fn get_storage_blockhash(&self, _: Self::Metadata) -> Result; @@ -406,7 +391,7 @@ impl RpcSol for RpcSolImpl { if signature_status == Some(Ok(())) { info!("airdrop signature ok"); - return Ok(bs58::encode(signature).into_string()); + return Ok(signature.to_string()); } else if now.elapsed().as_secs() > 5 { info!("airdrop signature timeout"); return Err(Error::internal_error()); @@ -437,7 +422,7 @@ impl RpcSol for RpcSolImpl { info!("send_transaction: send_to error: {:?}", err); Error::internal_error() })?; - let signature = bs58::encode(tx.signatures[0]).into_string(); + let signature = tx.signatures[0].to_string(); trace!( "send_transaction: sent {} bytes, signature={}", data.len(), @@ -455,6 +440,16 @@ impl RpcSol for RpcSolImpl { .to_string()) } + fn get_epoch_vote_accounts( + &self, + meta: Self::Metadata, + ) -> Result> { + meta.request_processor + .read() + .unwrap() + .get_epoch_vote_accounts() + } + fn get_storage_blockhash(&self, meta: Self::Metadata) -> Result { meta.request_processor .read() diff --git a/core/src/rpc_pubsub.rs b/core/src/rpc_pubsub.rs index a726e92e88..8a3228267d 100644 --- a/core/src/rpc_pubsub.rs +++ b/core/src/rpc_pubsub.rs @@ -1,7 +1,6 @@ //! The `pubsub` module implements a threaded subscription service on client RPC request use crate::rpc_subscriptions::{Confirmations, RpcSubscriptions}; -use bs58; use jsonrpc_core::{Error, ErrorCode, Result}; use jsonrpc_derive::rpc; use jsonrpc_pubsub::typed::Subscriber; @@ -10,7 +9,6 @@ use solana_sdk::account::Account; use solana_sdk::pubkey::Pubkey; use solana_sdk::signature::Signature; use solana_sdk::transaction; -use std::mem; use std::sync::{atomic, Arc}; #[rpc(server)] @@ -100,6 +98,16 @@ impl RpcSolPubSubImpl { } } +use std::str::FromStr; + +fn param(param_str: &str, thing: &str) -> Result { + param_str.parse::().map_err(|_e| Error { + code: ErrorCode::InvalidParams, + message: format!("Invalid Request: Invalid {} provided", thing), + data: None, + }) +} + impl RpcSolPubSub for RpcSolPubSubImpl { type Metadata = Arc; @@ -110,26 +118,18 @@ impl RpcSolPubSub for RpcSolPubSubImpl { pubkey_str: String, confirmations: Option, ) { - let pubkey_vec = bs58::decode(pubkey_str).into_vec().unwrap(); - if pubkey_vec.len() != mem::size_of::() { - subscriber - .reject(Error { - code: ErrorCode::InvalidParams, - message: "Invalid Request: Invalid pubkey provided".into(), - data: None, - }) - .unwrap(); - return; + match param::(&pubkey_str, "pubkey") { + Ok(pubkey) => { + let id = self.uid.fetch_add(1, atomic::Ordering::SeqCst); + let sub_id = SubscriptionId::Number(id as u64); + info!("account_subscribe: account={:?} id={:?}", pubkey, sub_id); + let sink = subscriber.assign_id(sub_id.clone()).unwrap(); + + self.subscriptions + .add_account_subscription(&pubkey, confirmations, &sub_id, &sink) + } + Err(e) => subscriber.reject(e).unwrap(), } - let pubkey = Pubkey::new(&pubkey_vec); - - let id = self.uid.fetch_add(1, atomic::Ordering::SeqCst); - let sub_id = SubscriptionId::Number(id as u64); - info!("account_subscribe: account={:?} id={:?}", pubkey, sub_id); - let sink = subscriber.assign_id(sub_id.clone()).unwrap(); - - self.subscriptions - .add_account_subscription(&pubkey, confirmations, &sub_id, &sink) } fn account_unsubscribe( @@ -156,26 +156,18 @@ impl RpcSolPubSub for RpcSolPubSubImpl { pubkey_str: String, confirmations: Option, ) { - let pubkey_vec = bs58::decode(pubkey_str).into_vec().unwrap(); - if pubkey_vec.len() != mem::size_of::() { - subscriber - .reject(Error { - code: ErrorCode::InvalidParams, - message: "Invalid Request: Invalid pubkey provided".into(), - data: None, - }) - .unwrap(); - return; + match param::(&pubkey_str, "pubkey") { + Ok(pubkey) => { + let id = self.uid.fetch_add(1, atomic::Ordering::SeqCst); + let sub_id = SubscriptionId::Number(id as u64); + info!("program_subscribe: account={:?} id={:?}", pubkey, sub_id); + let sink = subscriber.assign_id(sub_id.clone()).unwrap(); + + self.subscriptions + .add_program_subscription(&pubkey, confirmations, &sub_id, &sink) + } + Err(e) => subscriber.reject(e).unwrap(), } - let pubkey = Pubkey::new(&pubkey_vec); - - let id = self.uid.fetch_add(1, atomic::Ordering::SeqCst); - let sub_id = SubscriptionId::Number(id as u64); - info!("program_subscribe: account={:?} id={:?}", pubkey, sub_id); - let sink = subscriber.assign_id(sub_id.clone()).unwrap(); - - self.subscriptions - .add_program_subscription(&pubkey, confirmations, &sub_id, &sink) } fn program_unsubscribe( @@ -203,29 +195,25 @@ impl RpcSolPubSub for RpcSolPubSubImpl { confirmations: Option, ) { info!("signature_subscribe"); - let signature_vec = bs58::decode(signature_str).into_vec().unwrap(); - if signature_vec.len() != mem::size_of::() { - subscriber - .reject(Error { - code: ErrorCode::InvalidParams, - message: "Invalid Request: Invalid signature provided".into(), - data: None, - }) - .unwrap(); - return; + match param::(&signature_str, "signature") { + Ok(signature) => { + let id = self.uid.fetch_add(1, atomic::Ordering::SeqCst); + let sub_id = SubscriptionId::Number(id as u64); + info!( + "signature_subscribe: signature={:?} id={:?}", + signature, sub_id + ); + let sink = subscriber.assign_id(sub_id.clone()).unwrap(); + + self.subscriptions.add_signature_subscription( + &signature, + confirmations, + &sub_id, + &sink, + ); + } + Err(e) => subscriber.reject(e).unwrap(), } - let signature = Signature::new(&signature_vec); - - let id = self.uid.fetch_add(1, atomic::Ordering::SeqCst); - let sub_id = SubscriptionId::Number(id as u64); - info!( - "signature_subscribe: signature={:?} id={:?}", - signature, sub_id - ); - let sink = subscriber.assign_id(sub_id.clone()).unwrap(); - - self.subscriptions - .add_signature_subscription(&signature, confirmations, &sub_id, &sink); } fn signature_unsubscribe( diff --git a/core/src/rpc_subscriptions.rs b/core/src/rpc_subscriptions.rs index 8ec471b1db..85bdd87304 100644 --- a/core/src/rpc_subscriptions.rs +++ b/core/src/rpc_subscriptions.rs @@ -1,7 +1,6 @@ //! The `pubsub` module implements a threaded subscription service on client RPC request use crate::bank_forks::BankForks; -use bs58; use core::hash::Hash; use jsonrpc_core::futures::Future; use jsonrpc_pubsub::typed::Sink; @@ -144,7 +143,7 @@ where fn notify_program(accounts: Vec<(Pubkey, Account)>, sink: &Sink<(String, Account)>, _root: u64) { for (pubkey, account) in accounts.iter() { - sink.notify(Ok((bs58::encode(pubkey).into_string(), account.clone()))) + sink.notify(Ok((pubkey.to_string(), account.clone()))) .wait() .unwrap(); } diff --git a/programs/vote_api/src/vote_state.rs b/programs/vote_api/src/vote_state.rs index 466a352511..6980c7c71e 100644 --- a/programs/vote_api/src/vote_state.rs +++ b/programs/vote_api/src/vote_state.rs @@ -91,6 +91,11 @@ impl VoteState { serialized_size(&vote_state).unwrap() as usize } + // utility function, used by Stakes, tests + pub fn from(account: &Account) -> Option { + account.state().ok() + } + pub fn deserialize(input: &[u8]) -> Result { deserialize(input).map_err(|_| InstructionError::InvalidAccountData) }