From 24ab84936ecdec8ee404e4c25b5834b4a9fc7765 Mon Sep 17 00:00:00 2001 From: Michael Vines Date: Fri, 26 Feb 2021 21:42:09 -0800 Subject: [PATCH] Break up RPC API into three categories: minimal, full and admin --- Cargo.lock | 41 +- ci/localnet-sanity.sh | 14 +- ci/run-sanity.sh | 3 +- client/src/rpc_client.rs | 4 - client/src/rpc_request.rs | 2 - client/src/thin_client.rs | 4 - core/Cargo.toml | 2 +- core/src/rpc.rs | 2432 +++++++++--------- core/src/rpc_service.rs | 10 +- core/src/test_validator.rs | 46 +- core/src/validator.rs | 112 +- docs/src/developing/clients/jsonrpc-api.md | 53 - docs/src/proposals/cluster-test-framework.md | 1 - gossip/Cargo.toml | 1 - gossip/src/main.rs | 65 - local-cluster/src/cluster_tests.rs | 23 +- local-cluster/src/local_cluster.rs | 4 +- local-cluster/tests/local_cluster.rs | 65 +- multinode-demo/bootstrap-validator.sh | 2 - multinode-demo/validator.sh | 5 - run.sh | 1 - scripts/set-log-filter.sh | 27 - validator/Cargo.toml | 11 +- validator/src/admin_rpc_service.rs | 133 + validator/src/bin/solana-test-validator.rs | 111 +- validator/src/dashboard.rs | 280 +- validator/src/lib.rs | 59 +- validator/src/main.rs | 105 +- 28 files changed, 1784 insertions(+), 1832 deletions(-) delete mode 100755 scripts/set-log-filter.sh create mode 100644 validator/src/admin_rpc_service.rs diff --git a/Cargo.lock b/Cargo.lock index 219a5cac2a..c1c75b9396 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2000,7 +2000,9 @@ dependencies = [ "futures 0.3.8", "jsonrpc-core", "jsonrpc-pubsub", + "jsonrpc-server-utils", "log 0.4.11", + "parity-tokio-ipc", "serde", "serde_json", "tokio 0.2.22", @@ -2059,6 +2061,21 @@ dependencies = [ "unicase 2.6.0", ] +[[package]] +name = "jsonrpc-ipc-server" +version = "17.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c4cd89e5ea7e7f0884e828fc35bb83591a371b92439675eae28efa66c24a97" +dependencies = [ + "futures 0.3.8", + "jsonrpc-core", + "jsonrpc-server-utils", + "log 0.4.11", + "parity-tokio-ipc", + "parking_lot 0.11.0", + "tower-service", +] + [[package]] name = "jsonrpc-pubsub" version = "17.0.0" @@ -2667,6 +2684,22 @@ dependencies = [ "syn 1.0.60", ] +[[package]] +name = "parity-tokio-ipc" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd7f6c69d7687501b2205fe51ade1d7b8797bb3aa141fe5bf13dd78c0483bc89" +dependencies = [ + "futures 0.3.8", + "libc", + "log 0.4.11", + "mio-named-pipes", + "miow 0.3.6", + "rand 0.7.3", + "tokio 0.2.22", + "winapi 0.3.8", +] + [[package]] name = "parity-ws" version = "0.10.0" @@ -4590,7 +4623,6 @@ version = "1.6.0" dependencies = [ "clap", "solana-clap-utils", - "solana-client", "solana-core", "solana-logger 1.6.0", "solana-net-utils", @@ -5465,13 +5497,16 @@ dependencies = [ "core_affinity", "fd-lock", "indicatif", + "jsonrpc-core", + "jsonrpc-core-client", + "jsonrpc-derive", + "jsonrpc-ipc-server", + "jsonrpc-server-utils", "libc", "log 0.4.11", "num_cpus", "rand 0.7.3", "serde", - "serde_derive", - "serde_yaml", "signal-hook", "solana-clap-utils", "solana-cli-config", diff --git a/ci/localnet-sanity.sh b/ci/localnet-sanity.sh index 0a1d677869..8257758742 100755 --- a/ci/localnet-sanity.sh +++ b/ci/localnet-sanity.sh @@ -78,7 +78,6 @@ nodes=( --init-complete-file init-complete-node0.log \ --dynamic-port-range 8000-8050" "multinode-demo/validator.sh \ - --enable-rpc-exit \ --no-restart \ --dynamic-port-range 8050-8100 --init-complete-file init-complete-node1.log \ @@ -201,17 +200,10 @@ killNodes() { [[ ${#pids[@]} -gt 0 ]] || return # Try to use the RPC exit API to cleanly exit the first two nodes - # (dynamic nodes, -x, are just killed since their RPC port is not known) + # (dynamic nodes, -x, are just killed) echo "--- RPC exit" - for port in 8899 18899; do - ( - set -x - curl --retry 5 --retry-delay 2 --retry-connrefused \ - -X POST -H 'Content-Type: application/json' \ - -d '{"jsonrpc":"2.0","id":1, "method":"validatorExit"}' \ - http://localhost:$port - ) - done + $solana_validator --ledger "$SOLANA_CONFIG_DIR"/bootstrap-validator exit || true + $solana_validator --ledger "$SOLANA_CONFIG_DIR"/validator exit || true # Give the nodes a splash of time to cleanly exit before killing them sleep 2 diff --git a/ci/run-sanity.sh b/ci/run-sanity.sh index 36f423c53e..272e54f521 100755 --- a/ci/run-sanity.sh +++ b/ci/run-sanity.sh @@ -25,7 +25,8 @@ snapshot_slot=1 while [[ $($solana_cli --url http://localhost:8899 slot --commitment recent) -le $((snapshot_slot + 1)) ]]; do sleep 1 done -curl -X POST -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1, "method":"validatorExit"}' http://localhost:8899 + +$solana_validator --ledger config/ledger exit || true wait $pid diff --git a/client/src/rpc_client.rs b/client/src/rpc_client.rs index 40cd05e651..5d880fd168 100644 --- a/client/src/rpc_client.rs +++ b/client/src/rpc_client.rs @@ -1505,10 +1505,6 @@ impl RpcClient { } } - pub fn validator_exit(&self) -> ClientResult { - self.send(RpcRequest::ValidatorExit, Value::Null) - } - pub fn send(&self, request: RpcRequest, params: Value) -> ClientResult where T: serde::de::DeserializeOwned, diff --git a/client/src/rpc_request.rs b/client/src/rpc_request.rs index 68bb3123c8..9a8ec8ba43 100644 --- a/client/src/rpc_request.rs +++ b/client/src/rpc_request.rs @@ -7,7 +7,6 @@ use thiserror::Error; #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] pub enum RpcRequest { DeregisterNode, - ValidatorExit, GetAccountInfo, GetBalance, GetBlockTime, @@ -64,7 +63,6 @@ impl fmt::Display for RpcRequest { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let method = match self { RpcRequest::DeregisterNode => "deregisterNode", - RpcRequest::ValidatorExit => "validatorExit", RpcRequest::GetAccountInfo => "getAccountInfo", RpcRequest::GetBalance => "getBalance", RpcRequest::GetBlockTime => "getBlockTime", diff --git a/client/src/thin_client.rs b/client/src/thin_client.rs index 8cae5d414e..17b1236500 100644 --- a/client/src/thin_client.rs +++ b/client/src/thin_client.rs @@ -309,10 +309,6 @@ impl ThinClient { .map_err(|e| e.into()) } - pub fn validator_exit(&self) -> TransportResult { - self.rpc_client().validator_exit().map_err(|e| e.into()) - } - pub fn get_num_blocks_since_signature_confirmation( &mut self, sig: &Signature, diff --git a/core/Cargo.toml b/core/Cargo.toml index 4fba2bb87d..fb4faf5a24 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -29,7 +29,7 @@ flate2 = "1.0" indexmap = { version = "1.5", features = ["rayon"] } itertools = "0.9.0" jsonrpc-core = "17.0.0" -jsonrpc-core-client = { version = "17.0.0", features = ["ws"] } +jsonrpc-core-client = { version = "17.0.0", features = ["ipc", "ws"] } jsonrpc-derive = "17.0.0" jsonrpc-http-server = "17.0.0" jsonrpc-pubsub = "17.0.0" diff --git a/core/src/rpc.rs b/core/src/rpc.rs index dff6397ab2..3bd9d7a5e9 100644 --- a/core/src/rpc.rs +++ b/core/src/rpc.rs @@ -108,8 +108,6 @@ pub fn is_confirmed_rooted( #[derive(Debug, Default, Clone)] pub struct JsonRpcConfig { - pub enable_validator_exit: bool, - pub enable_set_log_filter: bool, pub enable_rpc_transaction_history: bool, pub enable_cpi_and_log_storage: bool, pub identity_pubkey: Pubkey, @@ -121,6 +119,7 @@ pub struct JsonRpcConfig { pub account_indexes: HashSet, pub rpc_threads: usize, pub rpc_bigtable_timeout: Option, + pub minimal_api: bool, } #[derive(Clone)] @@ -652,23 +651,6 @@ impl JsonRpcRequestProcessor { }) } - pub fn set_log_filter(&self, filter: String) { - if self.config.enable_set_log_filter { - solana_logger::setup_with(&filter); - } - } - - pub fn validator_exit(&self) -> bool { - if self.config.enable_validator_exit { - warn!("validator_exit request..."); - self.validator_exit.write().unwrap().exit(); - true - } else { - debug!("validator_exit ignored"); - false - } - } - fn check_blockstore_root( &self, result: &std::result::Result, @@ -1762,369 +1744,6 @@ fn get_mint_decimals(data: &[u8]) -> Result { .map(|mint| mint.decimals) } -#[rpc] -pub trait RpcSol { - type Metadata; - - // DEPRECATED - #[rpc(meta, name = "confirmTransaction")] - fn confirm_transaction( - &self, - meta: Self::Metadata, - signature_str: String, - commitment: Option, - ) -> Result>; - - // DEPRECATED - #[rpc(meta, name = "getSignatureStatus")] - fn get_signature_status( - &self, - meta: Self::Metadata, - signature_str: String, - commitment: Option, - ) -> Result>>; - - // DEPRECATED (used by Trust Wallet) - #[rpc(meta, name = "getSignatureConfirmation")] - fn get_signature_confirmation( - &self, - meta: Self::Metadata, - signature_str: String, - commitment: Option, - ) -> Result>; - - #[rpc(meta, name = "getAccountInfo")] - fn get_account_info( - &self, - meta: Self::Metadata, - pubkey_str: String, - config: Option, - ) -> Result>>; - - #[rpc(meta, name = "getMultipleAccounts")] - fn get_multiple_accounts( - &self, - meta: Self::Metadata, - pubkey_strs: Vec, - config: Option, - ) -> Result>>>; - - #[rpc(meta, name = "getProgramAccounts")] - fn get_program_accounts( - &self, - meta: Self::Metadata, - program_id_str: String, - config: Option, - ) -> Result>; - - #[rpc(meta, name = "getMinimumBalanceForRentExemption")] - fn get_minimum_balance_for_rent_exemption( - &self, - meta: Self::Metadata, - data_len: usize, - commitment: Option, - ) -> Result; - - #[rpc(meta, name = "getInflationGovernor")] - fn get_inflation_governor( - &self, - meta: Self::Metadata, - commitment: Option, - ) -> Result; - - #[rpc(meta, name = "getInflationRate")] - fn get_inflation_rate(&self, meta: Self::Metadata) -> Result; - - #[rpc(meta, name = "getEpochSchedule")] - fn get_epoch_schedule(&self, meta: Self::Metadata) -> Result; - - #[rpc(meta, name = "getBalance")] - fn get_balance( - &self, - meta: Self::Metadata, - pubkey_str: String, - commitment: Option, - ) -> Result>; - - #[rpc(meta, name = "getClusterNodes")] - fn get_cluster_nodes(&self, meta: Self::Metadata) -> Result>; - - #[rpc(meta, name = "getRecentPerformanceSamples")] - fn get_recent_performance_samples( - &self, - meta: Self::Metadata, - limit: Option, - ) -> Result>; - - #[rpc(meta, name = "getEpochInfo")] - fn get_epoch_info( - &self, - meta: Self::Metadata, - commitment: Option, - ) -> Result; - - #[rpc(meta, name = "getBlockCommitment")] - fn get_block_commitment( - &self, - meta: Self::Metadata, - block: Slot, - ) -> Result>; - - #[rpc(meta, name = "getGenesisHash")] - fn get_genesis_hash(&self, meta: Self::Metadata) -> Result; - - #[rpc(meta, name = "getHealth")] - fn get_health(&self, meta: Self::Metadata) -> Result; - - #[rpc(meta, name = "getLeaderSchedule")] - fn get_leader_schedule( - &self, - meta: Self::Metadata, - slot: Option, - commitment: Option, - ) -> Result>; - - #[rpc(meta, name = "getRecentBlockhash")] - fn get_recent_blockhash( - &self, - meta: Self::Metadata, - commitment: Option, - ) -> Result>; - - #[rpc(meta, name = "getFees")] - fn get_fees( - &self, - meta: Self::Metadata, - commitment: Option, - ) -> Result>; - - #[rpc(meta, name = "getFeeCalculatorForBlockhash")] - fn get_fee_calculator_for_blockhash( - &self, - meta: Self::Metadata, - blockhash: String, - commitment: Option, - ) -> Result>>; - - #[rpc(meta, name = "getFeeRateGovernor")] - fn get_fee_rate_governor( - &self, - meta: Self::Metadata, - ) -> Result>; - - #[rpc(meta, name = "getSnapshotSlot")] - fn get_snapshot_slot(&self, meta: Self::Metadata) -> Result; - - #[rpc(meta, name = "getSignatureStatuses")] - fn get_signature_statuses( - &self, - meta: Self::Metadata, - signature_strs: Vec, - config: Option, - ) -> Result>>>; - - #[rpc(meta, name = "getSlot")] - fn get_slot(&self, meta: Self::Metadata, commitment: Option) -> Result; - - #[rpc(meta, name = "getMaxRetransmitSlot")] - fn get_max_retransmit_slot(&self, meta: Self::Metadata) -> Result; - - #[rpc(meta, name = "getMaxShredInsertSlot")] - fn get_max_shred_insert_slot(&self, meta: Self::Metadata) -> Result; - - #[rpc(meta, name = "getTransactionCount")] - fn get_transaction_count( - &self, - meta: Self::Metadata, - commitment: Option, - ) -> Result; - - // DEPRECATED - #[rpc(meta, name = "getTotalSupply")] - fn get_total_supply( - &self, - meta: Self::Metadata, - commitment: Option, - ) -> Result; - - #[rpc(meta, name = "getLargestAccounts")] - fn get_largest_accounts( - &self, - meta: Self::Metadata, - config: Option, - ) -> Result>>; - - #[rpc(meta, name = "getSupply")] - fn get_supply( - &self, - meta: Self::Metadata, - commitment: Option, - ) -> Result>; - - #[rpc(meta, name = "requestAirdrop")] - fn request_airdrop( - &self, - meta: Self::Metadata, - pubkey_str: String, - lamports: u64, - commitment: Option, - ) -> Result; - - #[rpc(meta, name = "sendTransaction")] - fn send_transaction( - &self, - meta: Self::Metadata, - data: String, - config: Option, - ) -> Result; - - #[rpc(meta, name = "simulateTransaction")] - fn simulate_transaction( - &self, - meta: Self::Metadata, - data: String, - config: Option, - ) -> Result>; - - #[rpc(meta, name = "getSlotLeader")] - fn get_slot_leader( - &self, - meta: Self::Metadata, - 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, - meta: Self::Metadata, - commitment: Option, - ) -> Result; - - #[rpc(meta, name = "validatorExit")] - fn validator_exit(&self, meta: Self::Metadata) -> Result; - - #[rpc(meta, name = "getIdentity")] - fn get_identity(&self, meta: Self::Metadata) -> Result; - - #[rpc(meta, name = "getVersion")] - fn get_version(&self, meta: Self::Metadata) -> Result; - - #[rpc(meta, name = "setLogFilter")] - fn set_log_filter(&self, _meta: Self::Metadata, filter: String) -> Result<()>; - - #[rpc(meta, name = "getConfirmedBlock")] - fn get_confirmed_block( - &self, - meta: Self::Metadata, - slot: Slot, - encoding: Option, - ) -> Result>; - - #[rpc(meta, name = "getBlockTime")] - fn get_block_time(&self, meta: Self::Metadata, slot: Slot) -> Result>; - - #[rpc(meta, name = "getConfirmedBlocks")] - fn get_confirmed_blocks( - &self, - meta: Self::Metadata, - start_slot: Slot, - end_slot: Option, - ) -> Result>; - - #[rpc(meta, name = "getConfirmedBlocksWithLimit")] - fn get_confirmed_blocks_with_limit( - &self, - meta: Self::Metadata, - start_slot: Slot, - limit: usize, - ) -> Result>; - - #[rpc(meta, name = "getConfirmedTransaction")] - fn get_confirmed_transaction( - &self, - meta: Self::Metadata, - signature_str: String, - encoding: Option, - ) -> Result>; - - #[rpc(meta, name = "getConfirmedSignaturesForAddress")] - fn get_confirmed_signatures_for_address( - &self, - meta: Self::Metadata, - pubkey_str: String, - start_slot: Slot, - end_slot: Slot, - ) -> Result>; - - #[rpc(meta, name = "getConfirmedSignaturesForAddress2")] - fn get_confirmed_signatures_for_address2( - &self, - meta: Self::Metadata, - address: String, - config: Option, - ) -> Result>; - - #[rpc(meta, name = "getFirstAvailableBlock")] - fn get_first_available_block(&self, meta: Self::Metadata) -> Result; - - #[rpc(meta, name = "getStakeActivation")] - fn get_stake_activation( - &self, - meta: Self::Metadata, - pubkey_str: String, - config: Option, - ) -> Result; - - // SPL Token-specific RPC endpoints - // See https://github.com/solana-labs/solana-program-library/releases/tag/token-v2.0.0 for - // program details - - #[rpc(meta, name = "getTokenAccountBalance")] - fn get_token_account_balance( - &self, - meta: Self::Metadata, - pubkey_str: String, - commitment: Option, - ) -> Result>; - - #[rpc(meta, name = "getTokenSupply")] - fn get_token_supply( - &self, - meta: Self::Metadata, - mint_str: String, - commitment: Option, - ) -> Result>; - - #[rpc(meta, name = "getTokenLargestAccounts")] - fn get_token_largest_accounts( - &self, - meta: Self::Metadata, - mint_str: String, - commitment: Option, - ) -> Result>>; - - #[rpc(meta, name = "getTokenAccountsByOwner")] - fn get_token_accounts_by_owner( - &self, - meta: Self::Metadata, - owner_str: String, - token_account_filter: RpcTokenAccountsFilter, - config: Option, - ) -> Result>>; - - #[rpc(meta, name = "getTokenAccountsByDelegate")] - fn get_token_accounts_by_delegate( - &self, - meta: Self::Metadata, - delegate_str: String, - token_account_filter: RpcTokenAccountsFilter, - config: Option, - ) -> Result>>; -} - fn _send_transaction( meta: JsonRpcRequestProcessor, transaction: Transaction, @@ -2151,827 +1770,1219 @@ fn _send_transaction( Ok(signature.to_string()) } -pub struct RpcSolImpl; -impl RpcSol for RpcSolImpl { - type Metadata = JsonRpcRequestProcessor; +// Minimal RPC interface that trusted validators are expected to provide +pub mod rpc_minimal { + use super::*; + #[rpc] + pub trait Minimal { + type Metadata; - fn confirm_transaction( - &self, - meta: Self::Metadata, - id: String, - commitment: Option, - ) -> Result> { - debug!("confirm_transaction rpc request received: {:?}", id); - let signature = verify_signature(&id)?; - Ok(meta.confirm_transaction(&signature, commitment)) + #[rpc(meta, name = "getBalance")] + fn get_balance( + &self, + meta: Self::Metadata, + pubkey_str: String, + commitment: Option, + ) -> Result>; + + #[rpc(meta, name = "getEpochInfo")] + fn get_epoch_info( + &self, + meta: Self::Metadata, + commitment: Option, + ) -> Result; + + #[rpc(meta, name = "getHealth")] + fn get_health(&self, meta: Self::Metadata) -> Result; + + #[rpc(meta, name = "getIdentity")] + fn get_identity(&self, meta: Self::Metadata) -> Result; + + #[rpc(meta, name = "getSlot")] + fn get_slot( + &self, + meta: Self::Metadata, + commitment: Option, + ) -> Result; + + #[rpc(meta, name = "getSnapshotSlot")] + fn get_snapshot_slot(&self, meta: Self::Metadata) -> Result; + + #[rpc(meta, name = "getTransactionCount")] + fn get_transaction_count( + &self, + meta: Self::Metadata, + commitment: Option, + ) -> Result; + + #[rpc(meta, name = "getVersion")] + fn get_version(&self, meta: Self::Metadata) -> Result; + + // TODO: Refactor `solana-validator wait-for-restart-window` to not require this method, so + // it can be removed from rpc_minimal + #[rpc(meta, name = "getVoteAccounts")] + fn get_vote_accounts( + &self, + meta: Self::Metadata, + commitment: Option, + ) -> Result; + + // TODO: Refactor `solana-validator wait-for-restart-window` to not require this method, so + // it can be removed from rpc_minimal + #[rpc(meta, name = "getLeaderSchedule")] + fn get_leader_schedule( + &self, + meta: Self::Metadata, + slot: Option, + commitment: Option, + ) -> Result>; } - fn get_account_info( - &self, - meta: Self::Metadata, - pubkey_str: String, - config: Option, - ) -> Result>> { - debug!("get_account_info rpc request received: {:?}", pubkey_str); - let pubkey = verify_pubkey(pubkey_str)?; - meta.get_account_info(&pubkey, config) - } + pub struct MinimalImpl; + impl Minimal for MinimalImpl { + type Metadata = JsonRpcRequestProcessor; - fn get_multiple_accounts( - &self, - meta: Self::Metadata, - pubkey_strs: Vec, - config: Option, - ) -> Result>>> { - debug!( - "get_multiple_accounts rpc request received: {:?}", - pubkey_strs.len() - ); - - let max_multiple_accounts = meta - .config - .max_multiple_accounts - .unwrap_or(MAX_MULTIPLE_ACCOUNTS); - if pubkey_strs.len() > max_multiple_accounts { - return Err(Error::invalid_params(format!( - "Too many inputs provided; max {}", - max_multiple_accounts - ))); - } - let mut pubkeys: Vec = vec![]; - for pubkey_str in pubkey_strs { - pubkeys.push(verify_pubkey(pubkey_str)?); - } - meta.get_multiple_accounts(pubkeys, config) - } - - fn get_minimum_balance_for_rent_exemption( - &self, - meta: Self::Metadata, - data_len: usize, - commitment: Option, - ) -> Result { - debug!( - "get_minimum_balance_for_rent_exemption rpc request received: {:?}", - data_len - ); - if data_len as u64 > system_instruction::MAX_PERMITTED_DATA_LENGTH { - return Err(Error::invalid_request()); - } - Ok(meta.get_minimum_balance_for_rent_exemption(data_len, commitment)) - } - - fn get_program_accounts( - &self, - meta: Self::Metadata, - program_id_str: String, - config: Option, - ) -> Result> { - debug!( - "get_program_accounts rpc request received: {:?}", - program_id_str - ); - let program_id = verify_pubkey(program_id_str)?; - let (config, filters) = if let Some(config) = config { - ( - Some(config.account_config), - config.filters.unwrap_or_default(), - ) - } else { - (None, vec![]) - }; - if filters.len() > MAX_GET_PROGRAM_ACCOUNT_FILTERS { - return Err(Error::invalid_params(format!( - "Too many filters provided; max {}", - MAX_GET_PROGRAM_ACCOUNT_FILTERS - ))); - } - for filter in &filters { - verify_filter(filter)?; - } - meta.get_program_accounts(&program_id, config, filters) - } - - fn get_inflation_governor( - &self, - meta: Self::Metadata, - commitment: Option, - ) -> Result { - debug!("get_inflation_governor rpc request received"); - Ok(meta.get_inflation_governor(commitment)) - } - - fn get_inflation_rate(&self, meta: Self::Metadata) -> Result { - debug!("get_inflation_rate rpc request received"); - Ok(meta.get_inflation_rate()) - } - - fn get_epoch_schedule(&self, meta: Self::Metadata) -> Result { - debug!("get_epoch_schedule rpc request received"); - Ok(meta.get_epoch_schedule()) - } - - fn get_balance( - &self, - meta: Self::Metadata, - pubkey_str: String, - commitment: Option, - ) -> Result> { - debug!("get_balance rpc request received: {:?}", pubkey_str); - let pubkey = verify_pubkey(pubkey_str)?; - Ok(meta.get_balance(&pubkey, commitment)) - } - - fn get_recent_performance_samples( - &self, - meta: Self::Metadata, - limit: Option, - ) -> Result> { - debug!("get_recent_performance_samples request received"); - - let limit = limit.unwrap_or(PERFORMANCE_SAMPLES_LIMIT); - - if limit > PERFORMANCE_SAMPLES_LIMIT { - return Err(Error::invalid_params(format!( - "Invalid limit; max {}", - PERFORMANCE_SAMPLES_LIMIT - ))); + fn get_balance( + &self, + meta: Self::Metadata, + pubkey_str: String, + commitment: Option, + ) -> Result> { + debug!("get_balance rpc request received: {:?}", pubkey_str); + let pubkey = verify_pubkey(pubkey_str)?; + Ok(meta.get_balance(&pubkey, commitment)) } - Ok(meta - .blockstore - .get_recent_perf_samples(limit) - .map_err(|err| { - warn!("get_recent_performance_samples failed: {:?}", err); - Error::invalid_request() - })? - .iter() - .map(|(slot, sample)| RpcPerfSample { - slot: *slot, - num_transactions: sample.num_transactions, - num_slots: sample.num_slots, - sample_period_secs: sample.sample_period_secs, - }) - .collect()) - } + fn get_epoch_info( + &self, + meta: Self::Metadata, + commitment: Option, + ) -> Result { + debug!("get_epoch_info rpc request received"); + let bank = meta.bank(commitment); + Ok(bank.get_epoch_info()) + } - fn get_cluster_nodes(&self, meta: Self::Metadata) -> Result> { - debug!("get_cluster_nodes rpc request received"); - let cluster_info = &meta.cluster_info; - fn valid_address_or_none(addr: &SocketAddr) -> Option { - if ContactInfo::is_valid_address(addr) { - Some(*addr) - } else { - None + fn get_health(&self, meta: Self::Metadata) -> Result { + match meta.health.check() { + RpcHealthStatus::Ok => Ok("ok".to_string()), + RpcHealthStatus::Behind { num_slots } => Err(RpcCustomError::NodeUnhealthy { + num_slots_behind: Some(num_slots), + } + .into()), } } - let my_shred_version = cluster_info.my_shred_version(); - Ok(cluster_info - .all_peers() - .iter() - .filter_map(|(contact_info, _)| { - if my_shred_version == contact_info.shred_version - && ContactInfo::is_valid_address(&contact_info.gossip) - { - let (version, feature_set) = - if let Some(version) = cluster_info.get_node_version(&contact_info.id) { + + fn get_identity(&self, meta: Self::Metadata) -> Result { + debug!("get_identity rpc request received"); + Ok(RpcIdentity { + identity: meta.config.identity_pubkey.to_string(), + }) + } + + fn get_slot( + &self, + meta: Self::Metadata, + commitment: Option, + ) -> Result { + debug!("get_slot rpc request received"); + Ok(meta.get_slot(commitment)) + } + + fn get_snapshot_slot(&self, meta: Self::Metadata) -> Result { + debug!("get_snapshot_slot rpc request received"); + + meta.snapshot_config + .and_then(|snapshot_config| { + get_highest_snapshot_archive_path(&snapshot_config.snapshot_package_output_path) + .map(|(_, (slot, _, _))| slot) + }) + .ok_or_else(|| RpcCustomError::NoSnapshot.into()) + } + + fn get_transaction_count( + &self, + meta: Self::Metadata, + commitment: Option, + ) -> Result { + debug!("get_transaction_count rpc request received"); + Ok(meta.get_transaction_count(commitment)) + } + + fn get_version(&self, _: Self::Metadata) -> Result { + debug!("get_version rpc request received"); + let version = solana_version::Version::default(); + Ok(RpcVersionInfo { + solana_core: version.to_string(), + feature_set: Some(version.feature_set), + }) + } + + // TODO: Refactor `solana-validator wait-for-restart-window` to not require this method, so + // it can be removed from rpc_minimal + fn get_vote_accounts( + &self, + meta: Self::Metadata, + commitment: Option, + ) -> Result { + debug!("get_vote_accounts rpc request received"); + meta.get_vote_accounts(commitment) + } + + // TODO: Refactor `solana-validator wait-for-restart-window` to not require this method, so + // it can be removed from rpc_minimal + fn get_leader_schedule( + &self, + meta: Self::Metadata, + slot: Option, + commitment: Option, + ) -> Result> { + let bank = meta.bank(commitment); + let slot = slot.unwrap_or_else(|| bank.slot()); + let epoch = bank.epoch_schedule().get_epoch(slot); + + debug!("get_leader_schedule rpc request received: {:?}", slot); + + Ok( + solana_ledger::leader_schedule_utils::leader_schedule(epoch, &bank).map( + |leader_schedule| { + let mut leader_schedule_by_identity = HashMap::new(); + + for (slot_index, identity_pubkey) in + leader_schedule.get_slot_leaders().iter().enumerate() + { + leader_schedule_by_identity + .entry(identity_pubkey) + .or_insert_with(Vec::new) + .push(slot_index); + } + + leader_schedule_by_identity + .into_iter() + .map(|(identity_pubkey, slot_indices)| { + (identity_pubkey.to_string(), slot_indices) + }) + .collect() + }, + ), + ) + } + } +} + +// Full RPC interface that an API node is expected to provide +// (rpc_minimal should also be provided by an API node) +pub mod rpc_full { + use super::*; + #[rpc] + pub trait Full { + type Metadata; + + // DEPRECATED + #[rpc(meta, name = "confirmTransaction")] + fn confirm_transaction( + &self, + meta: Self::Metadata, + signature_str: String, + commitment: Option, + ) -> Result>; + + // DEPRECATED + #[rpc(meta, name = "getSignatureStatus")] + fn get_signature_status( + &self, + meta: Self::Metadata, + signature_str: String, + commitment: Option, + ) -> Result>>; + + // DEPRECATED (used by Trust Wallet) + #[rpc(meta, name = "getSignatureConfirmation")] + fn get_signature_confirmation( + &self, + meta: Self::Metadata, + signature_str: String, + commitment: Option, + ) -> Result>; + + #[rpc(meta, name = "getAccountInfo")] + fn get_account_info( + &self, + meta: Self::Metadata, + pubkey_str: String, + config: Option, + ) -> Result>>; + + #[rpc(meta, name = "getMultipleAccounts")] + fn get_multiple_accounts( + &self, + meta: Self::Metadata, + pubkey_strs: Vec, + config: Option, + ) -> Result>>>; + + #[rpc(meta, name = "getProgramAccounts")] + fn get_program_accounts( + &self, + meta: Self::Metadata, + program_id_str: String, + config: Option, + ) -> Result>; + + #[rpc(meta, name = "getMinimumBalanceForRentExemption")] + fn get_minimum_balance_for_rent_exemption( + &self, + meta: Self::Metadata, + data_len: usize, + commitment: Option, + ) -> Result; + + #[rpc(meta, name = "getInflationGovernor")] + fn get_inflation_governor( + &self, + meta: Self::Metadata, + commitment: Option, + ) -> Result; + + #[rpc(meta, name = "getInflationRate")] + fn get_inflation_rate(&self, meta: Self::Metadata) -> Result; + + #[rpc(meta, name = "getEpochSchedule")] + fn get_epoch_schedule(&self, meta: Self::Metadata) -> Result; + + #[rpc(meta, name = "getClusterNodes")] + fn get_cluster_nodes(&self, meta: Self::Metadata) -> Result>; + + #[rpc(meta, name = "getRecentPerformanceSamples")] + fn get_recent_performance_samples( + &self, + meta: Self::Metadata, + limit: Option, + ) -> Result>; + + #[rpc(meta, name = "getBlockCommitment")] + fn get_block_commitment( + &self, + meta: Self::Metadata, + block: Slot, + ) -> Result>; + + #[rpc(meta, name = "getGenesisHash")] + fn get_genesis_hash(&self, meta: Self::Metadata) -> Result; + + #[rpc(meta, name = "getRecentBlockhash")] + fn get_recent_blockhash( + &self, + meta: Self::Metadata, + commitment: Option, + ) -> Result>; + + #[rpc(meta, name = "getFees")] + fn get_fees( + &self, + meta: Self::Metadata, + commitment: Option, + ) -> Result>; + + #[rpc(meta, name = "getFeeCalculatorForBlockhash")] + fn get_fee_calculator_for_blockhash( + &self, + meta: Self::Metadata, + blockhash: String, + commitment: Option, + ) -> Result>>; + + #[rpc(meta, name = "getFeeRateGovernor")] + fn get_fee_rate_governor( + &self, + meta: Self::Metadata, + ) -> Result>; + + #[rpc(meta, name = "getSignatureStatuses")] + fn get_signature_statuses( + &self, + meta: Self::Metadata, + signature_strs: Vec, + config: Option, + ) -> Result>>>; + + #[rpc(meta, name = "getMaxRetransmitSlot")] + fn get_max_retransmit_slot(&self, meta: Self::Metadata) -> Result; + + #[rpc(meta, name = "getMaxShredInsertSlot")] + fn get_max_shred_insert_slot(&self, meta: Self::Metadata) -> Result; + + // DEPRECATED + #[rpc(meta, name = "getTotalSupply")] + fn get_total_supply( + &self, + meta: Self::Metadata, + commitment: Option, + ) -> Result; + + #[rpc(meta, name = "getLargestAccounts")] + fn get_largest_accounts( + &self, + meta: Self::Metadata, + config: Option, + ) -> Result>>; + + #[rpc(meta, name = "getSupply")] + fn get_supply( + &self, + meta: Self::Metadata, + commitment: Option, + ) -> Result>; + + #[rpc(meta, name = "requestAirdrop")] + fn request_airdrop( + &self, + meta: Self::Metadata, + pubkey_str: String, + lamports: u64, + commitment: Option, + ) -> Result; + + #[rpc(meta, name = "sendTransaction")] + fn send_transaction( + &self, + meta: Self::Metadata, + data: String, + config: Option, + ) -> Result; + + #[rpc(meta, name = "simulateTransaction")] + fn simulate_transaction( + &self, + meta: Self::Metadata, + data: String, + config: Option, + ) -> Result>; + + #[rpc(meta, name = "getSlotLeader")] + fn get_slot_leader( + &self, + meta: Self::Metadata, + commitment: Option, + ) -> Result; + + #[rpc(meta, name = "minimumLedgerSlot")] + fn minimum_ledger_slot(&self, meta: Self::Metadata) -> Result; + + #[rpc(meta, name = "getConfirmedBlock")] + fn get_confirmed_block( + &self, + meta: Self::Metadata, + slot: Slot, + encoding: Option, + ) -> Result>; + + #[rpc(meta, name = "getBlockTime")] + fn get_block_time(&self, meta: Self::Metadata, slot: Slot) + -> Result>; + + #[rpc(meta, name = "getConfirmedBlocks")] + fn get_confirmed_blocks( + &self, + meta: Self::Metadata, + start_slot: Slot, + end_slot: Option, + ) -> Result>; + + #[rpc(meta, name = "getConfirmedBlocksWithLimit")] + fn get_confirmed_blocks_with_limit( + &self, + meta: Self::Metadata, + start_slot: Slot, + limit: usize, + ) -> Result>; + + #[rpc(meta, name = "getConfirmedTransaction")] + fn get_confirmed_transaction( + &self, + meta: Self::Metadata, + signature_str: String, + encoding: Option, + ) -> Result>; + + #[rpc(meta, name = "getConfirmedSignaturesForAddress")] + fn get_confirmed_signatures_for_address( + &self, + meta: Self::Metadata, + pubkey_str: String, + start_slot: Slot, + end_slot: Slot, + ) -> Result>; + + #[rpc(meta, name = "getConfirmedSignaturesForAddress2")] + fn get_confirmed_signatures_for_address2( + &self, + meta: Self::Metadata, + address: String, + config: Option, + ) -> Result>; + + #[rpc(meta, name = "getFirstAvailableBlock")] + fn get_first_available_block(&self, meta: Self::Metadata) -> Result; + + #[rpc(meta, name = "getStakeActivation")] + fn get_stake_activation( + &self, + meta: Self::Metadata, + pubkey_str: String, + config: Option, + ) -> Result; + + // SPL Token-specific RPC endpoints + // See https://github.com/solana-labs/solana-program-library/releases/tag/token-v2.0.0 for + // program details + + #[rpc(meta, name = "getTokenAccountBalance")] + fn get_token_account_balance( + &self, + meta: Self::Metadata, + pubkey_str: String, + commitment: Option, + ) -> Result>; + + #[rpc(meta, name = "getTokenSupply")] + fn get_token_supply( + &self, + meta: Self::Metadata, + mint_str: String, + commitment: Option, + ) -> Result>; + + #[rpc(meta, name = "getTokenLargestAccounts")] + fn get_token_largest_accounts( + &self, + meta: Self::Metadata, + mint_str: String, + commitment: Option, + ) -> Result>>; + + #[rpc(meta, name = "getTokenAccountsByOwner")] + fn get_token_accounts_by_owner( + &self, + meta: Self::Metadata, + owner_str: String, + token_account_filter: RpcTokenAccountsFilter, + config: Option, + ) -> Result>>; + + #[rpc(meta, name = "getTokenAccountsByDelegate")] + fn get_token_accounts_by_delegate( + &self, + meta: Self::Metadata, + delegate_str: String, + token_account_filter: RpcTokenAccountsFilter, + config: Option, + ) -> Result>>; + } + + pub struct FullImpl; + impl Full for FullImpl { + type Metadata = JsonRpcRequestProcessor; + + fn confirm_transaction( + &self, + meta: Self::Metadata, + id: String, + commitment: Option, + ) -> Result> { + debug!("confirm_transaction rpc request received: {:?}", id); + let signature = verify_signature(&id)?; + Ok(meta.confirm_transaction(&signature, commitment)) + } + + fn get_account_info( + &self, + meta: Self::Metadata, + pubkey_str: String, + config: Option, + ) -> Result>> { + debug!("get_account_info rpc request received: {:?}", pubkey_str); + let pubkey = verify_pubkey(pubkey_str)?; + meta.get_account_info(&pubkey, config) + } + + fn get_multiple_accounts( + &self, + meta: Self::Metadata, + pubkey_strs: Vec, + config: Option, + ) -> Result>>> { + debug!( + "get_multiple_accounts rpc request received: {:?}", + pubkey_strs.len() + ); + + let max_multiple_accounts = meta + .config + .max_multiple_accounts + .unwrap_or(MAX_MULTIPLE_ACCOUNTS); + if pubkey_strs.len() > max_multiple_accounts { + return Err(Error::invalid_params(format!( + "Too many inputs provided; max {}", + max_multiple_accounts + ))); + } + let mut pubkeys: Vec = vec![]; + for pubkey_str in pubkey_strs { + pubkeys.push(verify_pubkey(pubkey_str)?); + } + meta.get_multiple_accounts(pubkeys, config) + } + + fn get_minimum_balance_for_rent_exemption( + &self, + meta: Self::Metadata, + data_len: usize, + commitment: Option, + ) -> Result { + debug!( + "get_minimum_balance_for_rent_exemption rpc request received: {:?}", + data_len + ); + if data_len as u64 > system_instruction::MAX_PERMITTED_DATA_LENGTH { + return Err(Error::invalid_request()); + } + Ok(meta.get_minimum_balance_for_rent_exemption(data_len, commitment)) + } + + fn get_program_accounts( + &self, + meta: Self::Metadata, + program_id_str: String, + config: Option, + ) -> Result> { + debug!( + "get_program_accounts rpc request received: {:?}", + program_id_str + ); + let program_id = verify_pubkey(program_id_str)?; + let (config, filters) = if let Some(config) = config { + ( + Some(config.account_config), + config.filters.unwrap_or_default(), + ) + } else { + (None, vec![]) + }; + if filters.len() > MAX_GET_PROGRAM_ACCOUNT_FILTERS { + return Err(Error::invalid_params(format!( + "Too many filters provided; max {}", + MAX_GET_PROGRAM_ACCOUNT_FILTERS + ))); + } + for filter in &filters { + verify_filter(filter)?; + } + meta.get_program_accounts(&program_id, config, filters) + } + + fn get_inflation_governor( + &self, + meta: Self::Metadata, + commitment: Option, + ) -> Result { + debug!("get_inflation_governor rpc request received"); + Ok(meta.get_inflation_governor(commitment)) + } + + fn get_inflation_rate(&self, meta: Self::Metadata) -> Result { + debug!("get_inflation_rate rpc request received"); + Ok(meta.get_inflation_rate()) + } + + fn get_epoch_schedule(&self, meta: Self::Metadata) -> Result { + debug!("get_epoch_schedule rpc request received"); + Ok(meta.get_epoch_schedule()) + } + + fn get_recent_performance_samples( + &self, + meta: Self::Metadata, + limit: Option, + ) -> Result> { + debug!("get_recent_performance_samples request received"); + + let limit = limit.unwrap_or(PERFORMANCE_SAMPLES_LIMIT); + + if limit > PERFORMANCE_SAMPLES_LIMIT { + return Err(Error::invalid_params(format!( + "Invalid limit; max {}", + PERFORMANCE_SAMPLES_LIMIT + ))); + } + + Ok(meta + .blockstore + .get_recent_perf_samples(limit) + .map_err(|err| { + warn!("get_recent_performance_samples failed: {:?}", err); + Error::invalid_request() + })? + .iter() + .map(|(slot, sample)| RpcPerfSample { + slot: *slot, + num_transactions: sample.num_transactions, + num_slots: sample.num_slots, + sample_period_secs: sample.sample_period_secs, + }) + .collect()) + } + + fn get_cluster_nodes(&self, meta: Self::Metadata) -> Result> { + debug!("get_cluster_nodes rpc request received"); + let cluster_info = &meta.cluster_info; + fn valid_address_or_none(addr: &SocketAddr) -> Option { + if ContactInfo::is_valid_address(addr) { + Some(*addr) + } else { + None + } + } + let my_shred_version = cluster_info.my_shred_version(); + Ok(cluster_info + .all_peers() + .iter() + .filter_map(|(contact_info, _)| { + if my_shred_version == contact_info.shred_version + && ContactInfo::is_valid_address(&contact_info.gossip) + { + let (version, feature_set) = if let Some(version) = + cluster_info.get_node_version(&contact_info.id) + { (Some(version.to_string()), Some(version.feature_set)) } else { (None, None) }; - Some(RpcContactInfo { - pubkey: contact_info.id.to_string(), - gossip: Some(contact_info.gossip), - tpu: valid_address_or_none(&contact_info.tpu), - rpc: valid_address_or_none(&contact_info.rpc), - version, - feature_set, - }) - } else { - None // Exclude spy nodes - } - }) - .collect()) - } - - fn get_epoch_info( - &self, - meta: Self::Metadata, - commitment: Option, - ) -> Result { - debug!("get_epoch_info rpc request received"); - let bank = meta.bank(commitment); - Ok(bank.get_epoch_info()) - } - - fn get_block_commitment( - &self, - meta: Self::Metadata, - block: Slot, - ) -> Result> { - debug!("get_block_commitment rpc request received"); - Ok(meta.get_block_commitment(block)) - } - - fn get_genesis_hash(&self, meta: Self::Metadata) -> Result { - debug!("get_genesis_hash rpc request received"); - Ok(meta.genesis_hash.to_string()) - } - - fn get_health(&self, meta: Self::Metadata) -> Result { - match meta.health.check() { - RpcHealthStatus::Ok => Ok("ok".to_string()), - RpcHealthStatus::Behind { num_slots } => Err(RpcCustomError::NodeUnhealthy { - num_slots_behind: Some(num_slots), - } - .into()), - } - } - - fn get_leader_schedule( - &self, - meta: Self::Metadata, - slot: Option, - commitment: Option, - ) -> Result> { - let bank = meta.bank(commitment); - let slot = slot.unwrap_or_else(|| bank.slot()); - let epoch = bank.epoch_schedule().get_epoch(slot); - - debug!("get_leader_schedule rpc request received: {:?}", slot); - - Ok( - solana_ledger::leader_schedule_utils::leader_schedule(epoch, &bank).map( - |leader_schedule| { - let mut leader_schedule_by_identity = HashMap::new(); - - for (slot_index, identity_pubkey) in - leader_schedule.get_slot_leaders().iter().enumerate() - { - leader_schedule_by_identity - .entry(identity_pubkey) - .or_insert_with(Vec::new) - .push(slot_index); - } - - leader_schedule_by_identity - .into_iter() - .map(|(identity_pubkey, slot_indices)| { - (identity_pubkey.to_string(), slot_indices) + Some(RpcContactInfo { + pubkey: contact_info.id.to_string(), + gossip: Some(contact_info.gossip), + tpu: valid_address_or_none(&contact_info.tpu), + rpc: valid_address_or_none(&contact_info.rpc), + version, + feature_set, }) - .collect() - }, - ), - ) - } - - fn get_recent_blockhash( - &self, - meta: Self::Metadata, - commitment: Option, - ) -> Result> { - debug!("get_recent_blockhash rpc request received"); - Ok(meta.get_recent_blockhash(commitment)) - } - - fn get_fees( - &self, - meta: Self::Metadata, - commitment: Option, - ) -> Result> { - debug!("get_fees rpc request received"); - Ok(meta.get_fees(commitment)) - } - - fn get_fee_calculator_for_blockhash( - &self, - meta: Self::Metadata, - blockhash: String, - commitment: Option, - ) -> Result>> { - debug!("get_fee_calculator_for_blockhash rpc request received"); - let blockhash = - Hash::from_str(&blockhash).map_err(|e| Error::invalid_params(format!("{:?}", e)))?; - Ok(meta.get_fee_calculator_for_blockhash(&blockhash, commitment)) - } - - fn get_fee_rate_governor( - &self, - meta: Self::Metadata, - ) -> Result> { - debug!("get_fee_rate_governor rpc request received"); - Ok(meta.get_fee_rate_governor()) - } - - fn get_signature_confirmation( - &self, - meta: Self::Metadata, - signature_str: String, - commitment: Option, - ) -> Result> { - debug!( - "get_signature_confirmation rpc request received: {:?}", - signature_str - ); - let signature = verify_signature(&signature_str)?; - Ok(meta.get_signature_confirmation_status(signature, commitment)) - } - - fn get_signature_status( - &self, - meta: Self::Metadata, - signature_str: String, - commitment: Option, - ) -> Result>> { - debug!( - "get_signature_status rpc request received: {:?}", - signature_str - ); - let signature = verify_signature(&signature_str)?; - Ok(meta.get_signature_status(signature, commitment)) - } - - fn get_snapshot_slot(&self, meta: Self::Metadata) -> Result { - debug!("get_snapshot_slot rpc request received"); - - meta.snapshot_config - .and_then(|snapshot_config| { - get_highest_snapshot_archive_path(&snapshot_config.snapshot_package_output_path) - .map(|(_, (slot, _, _))| slot) - }) - .ok_or_else(|| RpcCustomError::NoSnapshot.into()) - } - - fn get_signature_statuses( - &self, - meta: Self::Metadata, - signature_strs: Vec, - config: Option, - ) -> Result>>> { - debug!( - "get_signature_statuses rpc request received: {:?}", - signature_strs.len() - ); - if signature_strs.len() > MAX_GET_SIGNATURE_STATUSES_QUERY_ITEMS { - return Err(Error::invalid_params(format!( - "Too many inputs provided; max {}", - MAX_GET_SIGNATURE_STATUSES_QUERY_ITEMS - ))); - } - let mut signatures: Vec = vec![]; - for signature_str in signature_strs { - signatures.push(verify_signature(&signature_str)?); - } - meta.get_signature_statuses(signatures, config) - } - - fn get_slot(&self, meta: Self::Metadata, commitment: Option) -> Result { - debug!("get_slot rpc request received"); - Ok(meta.get_slot(commitment)) - } - - fn get_max_retransmit_slot(&self, meta: Self::Metadata) -> Result { - debug!("get_max_retransmit_slot rpc request received"); - Ok(meta.get_max_retransmit_slot()) - } - - fn get_max_shred_insert_slot(&self, meta: Self::Metadata) -> Result { - debug!("get_max_shred_insert_slot rpc request received"); - Ok(meta.get_max_shred_insert_slot()) - } - - fn get_transaction_count( - &self, - meta: Self::Metadata, - commitment: Option, - ) -> Result { - debug!("get_transaction_count rpc request received"); - Ok(meta.get_transaction_count(commitment)) - } - - fn get_total_supply( - &self, - meta: Self::Metadata, - commitment: Option, - ) -> Result { - debug!("get_total_supply rpc request received"); - Ok(meta.get_total_supply(commitment)) - } - - fn get_largest_accounts( - &self, - meta: Self::Metadata, - config: Option, - ) -> Result>> { - debug!("get_largest_accounts rpc request received"); - Ok(meta.get_largest_accounts(config)) - } - - fn get_supply( - &self, - meta: Self::Metadata, - commitment: Option, - ) -> Result> { - debug!("get_supply rpc request received"); - Ok(meta.get_supply(commitment)) - } - - fn request_airdrop( - &self, - meta: Self::Metadata, - pubkey_str: String, - lamports: u64, - commitment: Option, - ) -> Result { - debug!("request_airdrop rpc request received"); - trace!( - "request_airdrop id={} lamports={} commitment: {:?}", - pubkey_str, - lamports, - &commitment - ); - - let faucet_addr = meta.config.faucet_addr.ok_or_else(Error::invalid_request)?; - let pubkey = verify_pubkey(pubkey_str)?; - - let (blockhash, last_valid_slot) = { - let bank = meta.bank(commitment); - - let blockhash = bank.confirmed_last_blockhash().0; - ( - blockhash, - bank.get_blockhash_last_valid_slot(&blockhash).unwrap_or(0), - ) - }; - - let transaction = request_airdrop_transaction(&faucet_addr, &pubkey, lamports, blockhash) - .map_err(|err| { - info!("request_airdrop_transaction failed: {:?}", err); - Error::internal_error() - })?; - - let wire_transaction = serialize(&transaction).map_err(|err| { - info!("request_airdrop: serialize error: {:?}", err); - Error::internal_error() - })?; - - _send_transaction(meta, transaction, wire_transaction, last_valid_slot, None) - } - - fn send_transaction( - &self, - meta: Self::Metadata, - data: String, - config: Option, - ) -> Result { - debug!("send_transaction rpc request received"); - let config = config.unwrap_or_default(); - let encoding = config.encoding.unwrap_or(UiTransactionEncoding::Base58); - let (wire_transaction, transaction) = deserialize_transaction(data, encoding)?; - - let preflight_commitment = config - .preflight_commitment - .map(|commitment| CommitmentConfig { commitment }); - let preflight_bank = &*meta.bank(preflight_commitment); - - let mut last_valid_slot = preflight_bank - .get_blockhash_last_valid_slot(&transaction.message.recent_blockhash) - .unwrap_or(0); - - let durable_nonce_info = solana_sdk::transaction::uses_durable_nonce(&transaction) - .and_then(|nonce_ix| { - solana_sdk::transaction::get_nonce_pubkey_from_instruction(&nonce_ix, &transaction) - }) - .map(|&pubkey| (pubkey, transaction.message.recent_blockhash)); - if durable_nonce_info.is_some() { - // While it uses a defined constant, this last_valid_slot value is chosen arbitrarily. - // It provides a fallback timeout for durable-nonce transaction retries in case of - // malicious packing of the retry queue. Durable-nonce transactions are otherwise - // retried until the nonce is advanced. - last_valid_slot = preflight_bank.slot() + MAX_RECENT_BLOCKHASHES as u64; + } else { + None // Exclude spy nodes + } + }) + .collect()) } - if !config.skip_preflight { - if let Err(e) = verify_transaction(&transaction) { - return Err(e); + fn get_block_commitment( + &self, + meta: Self::Metadata, + block: Slot, + ) -> Result> { + debug!("get_block_commitment rpc request received"); + Ok(meta.get_block_commitment(block)) + } + + fn get_genesis_hash(&self, meta: Self::Metadata) -> Result { + debug!("get_genesis_hash rpc request received"); + Ok(meta.genesis_hash.to_string()) + } + + fn get_recent_blockhash( + &self, + meta: Self::Metadata, + commitment: Option, + ) -> Result> { + debug!("get_recent_blockhash rpc request received"); + Ok(meta.get_recent_blockhash(commitment)) + } + + fn get_fees( + &self, + meta: Self::Metadata, + commitment: Option, + ) -> Result> { + debug!("get_fees rpc request received"); + Ok(meta.get_fees(commitment)) + } + + fn get_fee_calculator_for_blockhash( + &self, + meta: Self::Metadata, + blockhash: String, + commitment: Option, + ) -> Result>> { + debug!("get_fee_calculator_for_blockhash rpc request received"); + let blockhash = Hash::from_str(&blockhash) + .map_err(|e| Error::invalid_params(format!("{:?}", e)))?; + Ok(meta.get_fee_calculator_for_blockhash(&blockhash, commitment)) + } + + fn get_fee_rate_governor( + &self, + meta: Self::Metadata, + ) -> Result> { + debug!("get_fee_rate_governor rpc request received"); + Ok(meta.get_fee_rate_governor()) + } + + fn get_signature_confirmation( + &self, + meta: Self::Metadata, + signature_str: String, + commitment: Option, + ) -> Result> { + debug!( + "get_signature_confirmation rpc request received: {:?}", + signature_str + ); + let signature = verify_signature(&signature_str)?; + Ok(meta.get_signature_confirmation_status(signature, commitment)) + } + + fn get_signature_status( + &self, + meta: Self::Metadata, + signature_str: String, + commitment: Option, + ) -> Result>> { + debug!( + "get_signature_status rpc request received: {:?}", + signature_str + ); + let signature = verify_signature(&signature_str)?; + Ok(meta.get_signature_status(signature, commitment)) + } + + fn get_signature_statuses( + &self, + meta: Self::Metadata, + signature_strs: Vec, + config: Option, + ) -> Result>>> { + debug!( + "get_signature_statuses rpc request received: {:?}", + signature_strs.len() + ); + if signature_strs.len() > MAX_GET_SIGNATURE_STATUSES_QUERY_ITEMS { + return Err(Error::invalid_params(format!( + "Too many inputs provided; max {}", + MAX_GET_SIGNATURE_STATUSES_QUERY_ITEMS + ))); + } + let mut signatures: Vec = vec![]; + for signature_str in signature_strs { + signatures.push(verify_signature(&signature_str)?); + } + meta.get_signature_statuses(signatures, config) + } + + fn get_max_retransmit_slot(&self, meta: Self::Metadata) -> Result { + debug!("get_max_retransmit_slot rpc request received"); + Ok(meta.get_max_retransmit_slot()) + } + + fn get_max_shred_insert_slot(&self, meta: Self::Metadata) -> Result { + debug!("get_max_shred_insert_slot rpc request received"); + Ok(meta.get_max_shred_insert_slot()) + } + + fn get_total_supply( + &self, + meta: Self::Metadata, + commitment: Option, + ) -> Result { + debug!("get_total_supply rpc request received"); + Ok(meta.get_total_supply(commitment)) + } + + fn get_largest_accounts( + &self, + meta: Self::Metadata, + config: Option, + ) -> Result>> { + debug!("get_largest_accounts rpc request received"); + Ok(meta.get_largest_accounts(config)) + } + + fn get_supply( + &self, + meta: Self::Metadata, + commitment: Option, + ) -> Result> { + debug!("get_supply rpc request received"); + Ok(meta.get_supply(commitment)) + } + + fn request_airdrop( + &self, + meta: Self::Metadata, + pubkey_str: String, + lamports: u64, + commitment: Option, + ) -> Result { + debug!("request_airdrop rpc request received"); + trace!( + "request_airdrop id={} lamports={} commitment: {:?}", + pubkey_str, + lamports, + &commitment + ); + + let faucet_addr = meta.config.faucet_addr.ok_or_else(Error::invalid_request)?; + let pubkey = verify_pubkey(pubkey_str)?; + + let (blockhash, last_valid_slot) = { + let bank = meta.bank(commitment); + + let blockhash = bank.confirmed_last_blockhash().0; + ( + blockhash, + bank.get_blockhash_last_valid_slot(&blockhash).unwrap_or(0), + ) + }; + + let transaction = + request_airdrop_transaction(&faucet_addr, &pubkey, lamports, blockhash).map_err( + |err| { + info!("request_airdrop_transaction failed: {:?}", err); + Error::internal_error() + }, + )?; + + let wire_transaction = serialize(&transaction).map_err(|err| { + info!("request_airdrop: serialize error: {:?}", err); + Error::internal_error() + })?; + + _send_transaction(meta, transaction, wire_transaction, last_valid_slot, None) + } + + fn send_transaction( + &self, + meta: Self::Metadata, + data: String, + config: Option, + ) -> Result { + debug!("send_transaction rpc request received"); + let config = config.unwrap_or_default(); + let encoding = config.encoding.unwrap_or(UiTransactionEncoding::Base58); + let (wire_transaction, transaction) = deserialize_transaction(data, encoding)?; + + let preflight_commitment = config + .preflight_commitment + .map(|commitment| CommitmentConfig { commitment }); + let preflight_bank = &*meta.bank(preflight_commitment); + + let mut last_valid_slot = preflight_bank + .get_blockhash_last_valid_slot(&transaction.message.recent_blockhash) + .unwrap_or(0); + + let durable_nonce_info = solana_sdk::transaction::uses_durable_nonce(&transaction) + .and_then(|nonce_ix| { + solana_sdk::transaction::get_nonce_pubkey_from_instruction( + &nonce_ix, + &transaction, + ) + }) + .map(|&pubkey| (pubkey, transaction.message.recent_blockhash)); + if durable_nonce_info.is_some() { + // While it uses a defined constant, this last_valid_slot value is chosen arbitrarily. + // It provides a fallback timeout for durable-nonce transaction retries in case of + // malicious packing of the retry queue. Durable-nonce transactions are otherwise + // retried until the nonce is advanced. + last_valid_slot = preflight_bank.slot() + MAX_RECENT_BLOCKHASHES as u64; } - match meta.health.check() { - RpcHealthStatus::Ok => (), - RpcHealthStatus::Behind { num_slots } => { - return Err(RpcCustomError::NodeUnhealthy { - num_slots_behind: Some(num_slots), + if !config.skip_preflight { + if let Err(e) = verify_transaction(&transaction) { + return Err(e); + } + + match meta.health.check() { + RpcHealthStatus::Ok => (), + RpcHealthStatus::Behind { num_slots } => { + return Err(RpcCustomError::NodeUnhealthy { + num_slots_behind: Some(num_slots), + } + .into()); + } + } + + if let (Err(err), logs) = preflight_bank.simulate_transaction(transaction.clone()) { + return Err(RpcCustomError::SendTransactionPreflightFailure { + message: format!("Transaction simulation failed: {}", err), + result: RpcSimulateTransactionResult { + err: Some(err), + logs: Some(logs), + }, } .into()); } } - if let (Err(err), logs) = preflight_bank.simulate_transaction(transaction.clone()) { - return Err(RpcCustomError::SendTransactionPreflightFailure { - message: format!("Transaction simulation failed: {}", err), - result: RpcSimulateTransactionResult { - err: Some(err), - logs: Some(logs), - }, + _send_transaction( + meta, + transaction, + wire_transaction, + last_valid_slot, + durable_nonce_info, + ) + } + + fn simulate_transaction( + &self, + meta: Self::Metadata, + data: String, + config: Option, + ) -> Result> { + debug!("simulate_transaction rpc request received"); + let config = config.unwrap_or_default(); + let encoding = config.encoding.unwrap_or(UiTransactionEncoding::Base58); + let (_, transaction) = deserialize_transaction(data, encoding)?; + + if config.sig_verify { + if let Err(e) = verify_transaction(&transaction) { + return Err(e); } - .into()); } + + let bank = &*meta.bank(config.commitment); + let (result, logs) = bank.simulate_transaction(transaction); + + Ok(new_response( + &bank, + RpcSimulateTransactionResult { + err: result.err(), + logs: Some(logs), + }, + )) } - _send_transaction( - meta, - transaction, - wire_transaction, - last_valid_slot, - durable_nonce_info, - ) - } - - fn simulate_transaction( - &self, - meta: Self::Metadata, - data: String, - config: Option, - ) -> Result> { - debug!("simulate_transaction rpc request received"); - let config = config.unwrap_or_default(); - let encoding = config.encoding.unwrap_or(UiTransactionEncoding::Base58); - let (_, transaction) = deserialize_transaction(data, encoding)?; - - if config.sig_verify { - if let Err(e) = verify_transaction(&transaction) { - return Err(e); - } + fn get_slot_leader( + &self, + meta: Self::Metadata, + commitment: Option, + ) -> Result { + debug!("get_slot_leader rpc request received"); + Ok(meta.get_slot_leader(commitment)) } - let bank = &*meta.bank(config.commitment); - let (result, logs) = bank.simulate_transaction(transaction); + fn minimum_ledger_slot(&self, meta: Self::Metadata) -> Result { + debug!("minimum_ledger_slot rpc request received"); + meta.minimum_ledger_slot() + } - Ok(new_response( - &bank, - RpcSimulateTransactionResult { - err: result.err(), - logs: Some(logs), - }, - )) - } + fn get_confirmed_block( + &self, + meta: Self::Metadata, + slot: Slot, + encoding: Option, + ) -> Result> { + debug!("get_confirmed_block rpc request received: {:?}", slot); + meta.get_confirmed_block(slot, encoding) + } - fn get_slot_leader( - &self, - meta: Self::Metadata, - commitment: Option, - ) -> Result { - debug!("get_slot_leader rpc request received"); - Ok(meta.get_slot_leader(commitment)) - } - - fn minimum_ledger_slot(&self, meta: Self::Metadata) -> Result { - debug!("minimum_ledger_slot rpc request received"); - meta.minimum_ledger_slot() - } - - fn get_vote_accounts( - &self, - meta: Self::Metadata, - commitment: Option, - ) -> Result { - debug!("get_vote_accounts rpc request received"); - meta.get_vote_accounts(commitment) - } - - fn validator_exit(&self, meta: Self::Metadata) -> Result { - debug!("validator_exit rpc request received"); - Ok(meta.validator_exit()) - } - - fn get_identity(&self, meta: Self::Metadata) -> Result { - debug!("get_identity rpc request received"); - Ok(RpcIdentity { - identity: meta.config.identity_pubkey.to_string(), - }) - } - - fn get_version(&self, _: Self::Metadata) -> Result { - debug!("get_version rpc request received"); - let version = solana_version::Version::default(); - Ok(RpcVersionInfo { - solana_core: version.to_string(), - feature_set: Some(version.feature_set), - }) - } - - fn set_log_filter(&self, meta: Self::Metadata, filter: String) -> Result<()> { - debug!("set_log_filter rpc request received"); - meta.set_log_filter(filter); - Ok(()) - } - - fn get_confirmed_block( - &self, - meta: Self::Metadata, - slot: Slot, - encoding: Option, - ) -> Result> { - debug!("get_confirmed_block rpc request received: {:?}", slot); - meta.get_confirmed_block(slot, encoding) - } - - fn get_confirmed_blocks( - &self, - meta: Self::Metadata, - start_slot: Slot, - end_slot: Option, - ) -> Result> { - debug!( - "get_confirmed_blocks rpc request received: {}-{:?}", - start_slot, end_slot - ); - meta.get_confirmed_blocks(start_slot, end_slot) - } - - fn get_confirmed_blocks_with_limit( - &self, - meta: Self::Metadata, - start_slot: Slot, - limit: usize, - ) -> Result> { - debug!( - "get_confirmed_blocks_with_limit rpc request received: {}-{}", - start_slot, limit, - ); - meta.get_confirmed_blocks_with_limit(start_slot, limit) - } - - fn get_block_time(&self, meta: Self::Metadata, slot: Slot) -> Result> { - meta.get_block_time(slot) - } - - fn get_confirmed_transaction( - &self, - meta: Self::Metadata, - signature_str: String, - encoding: Option, - ) -> Result> { - debug!( - "get_confirmed_transaction rpc request received: {:?}", - signature_str - ); - let signature = verify_signature(&signature_str)?; - Ok(meta.get_confirmed_transaction(signature, encoding)) - } - - fn get_confirmed_signatures_for_address( - &self, - meta: Self::Metadata, - pubkey_str: String, - start_slot: Slot, - end_slot: Slot, - ) -> Result> { - debug!( - "get_confirmed_signatures_for_address rpc request received: {:?} {:?}-{:?}", - pubkey_str, start_slot, end_slot - ); - let pubkey = verify_pubkey(pubkey_str)?; - if end_slot < start_slot { - return Err(Error::invalid_params(format!( - "start_slot {} must be less than or equal to end_slot {}", + fn get_confirmed_blocks( + &self, + meta: Self::Metadata, + start_slot: Slot, + end_slot: Option, + ) -> Result> { + debug!( + "get_confirmed_blocks rpc request received: {}-{:?}", start_slot, end_slot - ))); - } - if end_slot - start_slot > MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS_SLOT_RANGE { - return Err(Error::invalid_params(format!( - "Slot range too large; max {}", - MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS_SLOT_RANGE - ))); - } - Ok(meta - .get_confirmed_signatures_for_address(pubkey, start_slot, end_slot) - .iter() - .map(|signature| signature.to_string()) - .collect()) - } - - fn get_confirmed_signatures_for_address2( - &self, - meta: Self::Metadata, - address: String, - config: Option, - ) -> Result> { - let address = verify_pubkey(address)?; - - let config = config.unwrap_or_default(); - let before = if let Some(before) = config.before { - Some(verify_signature(&before)?) - } else { - None - }; - let until = if let Some(until) = config.until { - Some(verify_signature(&until)?) - } else { - None - }; - let limit = config - .limit - .unwrap_or(MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS2_LIMIT); - - if limit == 0 || limit > MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS2_LIMIT { - return Err(Error::invalid_params(format!( - "Invalid limit; max {}", - MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS2_LIMIT - ))); + ); + meta.get_confirmed_blocks(start_slot, end_slot) } - meta.get_confirmed_signatures_for_address2(address, before, until, limit) - } + fn get_confirmed_blocks_with_limit( + &self, + meta: Self::Metadata, + start_slot: Slot, + limit: usize, + ) -> Result> { + debug!( + "get_confirmed_blocks_with_limit rpc request received: {}-{}", + start_slot, limit, + ); + meta.get_confirmed_blocks_with_limit(start_slot, limit) + } - fn get_first_available_block(&self, meta: Self::Metadata) -> Result { - debug!("get_first_available_block rpc request received"); - Ok(meta.get_first_available_block()) - } + fn get_block_time( + &self, + meta: Self::Metadata, + slot: Slot, + ) -> Result> { + meta.get_block_time(slot) + } - fn get_stake_activation( - &self, - meta: Self::Metadata, - pubkey_str: String, - config: Option, - ) -> Result { - debug!( - "get_stake_activation rpc request received: {:?}", - pubkey_str - ); - let pubkey = verify_pubkey(pubkey_str)?; - meta.get_stake_activation(&pubkey, config) - } + fn get_confirmed_transaction( + &self, + meta: Self::Metadata, + signature_str: String, + encoding: Option, + ) -> Result> { + debug!( + "get_confirmed_transaction rpc request received: {:?}", + signature_str + ); + let signature = verify_signature(&signature_str)?; + Ok(meta.get_confirmed_transaction(signature, encoding)) + } - fn get_token_account_balance( - &self, - meta: Self::Metadata, - pubkey_str: String, - commitment: Option, - ) -> Result> { - debug!( - "get_token_account_balance rpc request received: {:?}", - pubkey_str - ); - let pubkey = verify_pubkey(pubkey_str)?; - meta.get_token_account_balance(&pubkey, commitment) - } + fn get_confirmed_signatures_for_address( + &self, + meta: Self::Metadata, + pubkey_str: String, + start_slot: Slot, + end_slot: Slot, + ) -> Result> { + debug!( + "get_confirmed_signatures_for_address rpc request received: {:?} {:?}-{:?}", + pubkey_str, start_slot, end_slot + ); + let pubkey = verify_pubkey(pubkey_str)?; + if end_slot < start_slot { + return Err(Error::invalid_params(format!( + "start_slot {} must be less than or equal to end_slot {}", + start_slot, end_slot + ))); + } + if end_slot - start_slot > MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS_SLOT_RANGE { + return Err(Error::invalid_params(format!( + "Slot range too large; max {}", + MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS_SLOT_RANGE + ))); + } + Ok(meta + .get_confirmed_signatures_for_address(pubkey, start_slot, end_slot) + .iter() + .map(|signature| signature.to_string()) + .collect()) + } - fn get_token_supply( - &self, - meta: Self::Metadata, - mint_str: String, - commitment: Option, - ) -> Result> { - debug!("get_token_supply rpc request received: {:?}", mint_str); - let mint = verify_pubkey(mint_str)?; - meta.get_token_supply(&mint, commitment) - } + fn get_confirmed_signatures_for_address2( + &self, + meta: Self::Metadata, + address: String, + config: Option, + ) -> Result> { + let address = verify_pubkey(address)?; - fn get_token_largest_accounts( - &self, - meta: Self::Metadata, - mint_str: String, - commitment: Option, - ) -> Result>> { - debug!( - "get_token_largest_accounts rpc request received: {:?}", - mint_str - ); - let mint = verify_pubkey(mint_str)?; - meta.get_token_largest_accounts(&mint, commitment) - } + let config = config.unwrap_or_default(); + let before = if let Some(before) = config.before { + Some(verify_signature(&before)?) + } else { + None + }; + let until = if let Some(until) = config.until { + Some(verify_signature(&until)?) + } else { + None + }; + let limit = config + .limit + .unwrap_or(MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS2_LIMIT); - fn get_token_accounts_by_owner( - &self, - meta: Self::Metadata, - owner_str: String, - token_account_filter: RpcTokenAccountsFilter, - config: Option, - ) -> Result>> { - debug!( - "get_token_accounts_by_owner rpc request received: {:?}", - owner_str - ); - let owner = verify_pubkey(owner_str)?; - let token_account_filter = verify_token_account_filter(token_account_filter)?; - meta.get_token_accounts_by_owner(&owner, token_account_filter, config) - } + if limit == 0 || limit > MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS2_LIMIT { + return Err(Error::invalid_params(format!( + "Invalid limit; max {}", + MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS2_LIMIT + ))); + } - fn get_token_accounts_by_delegate( - &self, - meta: Self::Metadata, - delegate_str: String, - token_account_filter: RpcTokenAccountsFilter, - config: Option, - ) -> Result>> { - debug!( - "get_token_accounts_by_delegate rpc request received: {:?}", - delegate_str - ); - let delegate = verify_pubkey(delegate_str)?; - let token_account_filter = verify_token_account_filter(token_account_filter)?; - meta.get_token_accounts_by_delegate(&delegate, token_account_filter, config) + meta.get_confirmed_signatures_for_address2(address, before, until, limit) + } + + fn get_first_available_block(&self, meta: Self::Metadata) -> Result { + debug!("get_first_available_block rpc request received"); + Ok(meta.get_first_available_block()) + } + + fn get_stake_activation( + &self, + meta: Self::Metadata, + pubkey_str: String, + config: Option, + ) -> Result { + debug!( + "get_stake_activation rpc request received: {:?}", + pubkey_str + ); + let pubkey = verify_pubkey(pubkey_str)?; + meta.get_stake_activation(&pubkey, config) + } + + fn get_token_account_balance( + &self, + meta: Self::Metadata, + pubkey_str: String, + commitment: Option, + ) -> Result> { + debug!( + "get_token_account_balance rpc request received: {:?}", + pubkey_str + ); + let pubkey = verify_pubkey(pubkey_str)?; + meta.get_token_account_balance(&pubkey, commitment) + } + + fn get_token_supply( + &self, + meta: Self::Metadata, + mint_str: String, + commitment: Option, + ) -> Result> { + debug!("get_token_supply rpc request received: {:?}", mint_str); + let mint = verify_pubkey(mint_str)?; + meta.get_token_supply(&mint, commitment) + } + + fn get_token_largest_accounts( + &self, + meta: Self::Metadata, + mint_str: String, + commitment: Option, + ) -> Result>> { + debug!( + "get_token_largest_accounts rpc request received: {:?}", + mint_str + ); + let mint = verify_pubkey(mint_str)?; + meta.get_token_largest_accounts(&mint, commitment) + } + + fn get_token_accounts_by_owner( + &self, + meta: Self::Metadata, + owner_str: String, + token_account_filter: RpcTokenAccountsFilter, + config: Option, + ) -> Result>> { + debug!( + "get_token_accounts_by_owner rpc request received: {:?}", + owner_str + ); + let owner = verify_pubkey(owner_str)?; + let token_account_filter = verify_token_account_filter(token_account_filter)?; + meta.get_token_accounts_by_owner(&owner, token_account_filter, config) + } + + fn get_token_accounts_by_delegate( + &self, + meta: Self::Metadata, + delegate_str: String, + token_account_filter: RpcTokenAccountsFilter, + config: Option, + ) -> Result>> { + debug!( + "get_token_accounts_by_delegate rpc request received: {:?}", + delegate_str + ); + let delegate = verify_pubkey(delegate_str)?; + let token_account_filter = verify_token_account_filter(token_account_filter)?; + meta.get_token_accounts_by_delegate(&delegate, token_account_filter, config) + } } } @@ -3056,7 +3067,7 @@ pub(crate) fn create_validator_exit(exit: &Arc) -> Arc u64 { + async fn use_client(client: rpc_minimal::gen_client::Client, mint_pubkey: Pubkey) -> u64 { client .get_balance(mint_pubkey.to_string(), None) .await @@ -3348,7 +3359,7 @@ pub mod tests { let fut = async { let (client, server) = - local::connect_with_metadata::(&io, meta); + local::connect_with_metadata::(&io, meta); let client = use_client(client, mint_pubkey); futures::join!(client, server) @@ -3477,7 +3488,7 @@ pub mod tests { let meta = JsonRpcRequestProcessor::new_from_bank(&bank); let mut io = MetaIoHandler::default(); - io.extend_with(RpcSolImpl.to_delegate()); + io.extend_with(rpc_minimal::MinimalImpl.to_delegate()); let req = r#"{"jsonrpc":"2.0","id":1,"method":"getTransactionCount"}"#; let res = io.handle_request_sync(&req, meta); @@ -4630,8 +4641,7 @@ pub mod tests { let meta = JsonRpcRequestProcessor::new_from_bank(&bank); let mut io = MetaIoHandler::default(); - let rpc = RpcSolImpl; - io.extend_with(rpc.to_delegate()); + io.extend_with(rpc_full::FullImpl.to_delegate()); let req = r#"{"jsonrpc":"2.0","id":1,"method":"sendTransaction","params":["37u9WtQpcm6ULa3Vmu7ySnANv"]}"#; let res = io.handle_request_sync(req, meta); @@ -4654,8 +4664,7 @@ pub mod tests { bank_forks.write().unwrap().get(0).unwrap().freeze(); let mut io = MetaIoHandler::default(); - let rpc = RpcSolImpl; - io.extend_with(rpc.to_delegate()); + io.extend_with(rpc_full::FullImpl.to_delegate()); let cluster_info = Arc::new(ClusterInfo::new_with_invalid_keypair( ContactInfo::new_with_socketaddr(&socketaddr!("127.0.0.1:1234")), )); @@ -4848,72 +4857,6 @@ pub mod tests { ) } - #[test] - fn test_rpc_request_processor_config_default_trait_validator_exit_fails() { - let exit = Arc::new(AtomicBool::new(false)); - let validator_exit = create_validator_exit(&exit); - let ledger_path = get_tmp_ledger_path!(); - let blockstore = Arc::new(Blockstore::open(&ledger_path).unwrap()); - let block_commitment_cache = Arc::new(RwLock::new(BlockCommitmentCache::default())); - let cluster_info = Arc::new(ClusterInfo::default()); - let tpu_address = cluster_info.my_contact_info().tpu; - let bank_forks = new_bank_forks().0; - let (request_processor, receiver) = JsonRpcRequestProcessor::new( - JsonRpcConfig::default(), - None, - bank_forks.clone(), - block_commitment_cache, - blockstore, - validator_exit, - RpcHealth::stub(), - cluster_info, - Hash::default(), - Arc::new(tokio::runtime::Runtime::new().unwrap()), - None, - OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks), - Arc::new(RwLock::new(LargestAccountsCache::new(30))), - Arc::new(MaxSlots::default()), - ); - SendTransactionService::new(tpu_address, &bank_forks, None, receiver, 1000, 1); - assert_eq!(request_processor.validator_exit(), false); - assert_eq!(exit.load(Ordering::Relaxed), false); - } - - #[test] - fn test_rpc_request_processor_allow_validator_exit_config() { - let exit = Arc::new(AtomicBool::new(false)); - let validator_exit = create_validator_exit(&exit); - let ledger_path = get_tmp_ledger_path!(); - let blockstore = Arc::new(Blockstore::open(&ledger_path).unwrap()); - let block_commitment_cache = Arc::new(RwLock::new(BlockCommitmentCache::default())); - let config = JsonRpcConfig { - enable_validator_exit: true, - ..JsonRpcConfig::default() - }; - let bank_forks = new_bank_forks().0; - let cluster_info = Arc::new(ClusterInfo::default()); - let tpu_address = cluster_info.my_contact_info().tpu; - let (request_processor, receiver) = JsonRpcRequestProcessor::new( - config, - None, - bank_forks.clone(), - block_commitment_cache, - blockstore, - validator_exit, - RpcHealth::stub(), - cluster_info, - Hash::default(), - Arc::new(tokio::runtime::Runtime::new().unwrap()), - None, - OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks), - Arc::new(RwLock::new(LargestAccountsCache::new(30))), - Arc::new(MaxSlots::default()), - ); - SendTransactionService::new(tpu_address, &bank_forks, None, receiver, 1000, 1); - assert_eq!(request_processor.validator_exit(), true); - assert_eq!(exit.load(Ordering::Relaxed), true); - } - #[test] fn test_rpc_get_identity() { let bob_pubkey = solana_sdk::pubkey::new_rand(); @@ -4999,14 +4942,10 @@ pub mod tests { CommitmentSlots::new_from_slot(bank_forks.read().unwrap().highest_slot()), ))); - let config = JsonRpcConfig { - enable_validator_exit: true, - ..JsonRpcConfig::default() - }; let cluster_info = Arc::new(ClusterInfo::default()); let tpu_address = cluster_info.my_contact_info().tpu; let (request_processor, receiver) = JsonRpcRequestProcessor::new( - config, + JsonRpcConfig::default(), None, bank_forks.clone(), block_commitment_cache, @@ -6261,7 +6200,8 @@ pub mod tests { ); let mut io = MetaIoHandler::default(); - io.extend_with(RpcSolImpl.to_delegate()); + io.extend_with(rpc_minimal::MinimalImpl.to_delegate()); + io.extend_with(rpc_full::FullImpl.to_delegate()); let req = r#"{"jsonrpc":"2.0","id":1,"method":"getSlot","params":[{"commitment":"confirmed"}]}"#; diff --git a/core/src/rpc_service.rs b/core/src/rpc_service.rs index 30513633c5..870127a32b 100644 --- a/core/src/rpc_service.rs +++ b/core/src/rpc_service.rs @@ -6,7 +6,7 @@ use crate::{ max_slots::MaxSlots, optimistically_confirmed_bank_tracker::OptimisticallyConfirmedBank, poh_recorder::PohRecorder, - rpc::*, + rpc::{rpc_full::*, rpc_minimal::*, *}, rpc_health::*, send_transaction_service::{LeaderInfo, SendTransactionService}, validator::ValidatorExit, @@ -337,6 +337,7 @@ impl JsonRpcService { (None, None) }; + let minimal_api = config.minimal_api; let (request_processor, receiver) = JsonRpcRequestProcessor::new( config, snapshot_config.clone(), @@ -392,8 +393,11 @@ impl JsonRpcService { .name("solana-jsonrpc".to_string()) .spawn(move || { let mut io = MetaIoHandler::default(); - let rpc = RpcSolImpl; - io.extend_with(rpc.to_delegate()); + + io.extend_with(rpc_minimal::MinimalImpl.to_delegate()); + if !minimal_api { + io.extend_with(rpc_full::FullImpl.to_delegate()); + } let request_middleware = RpcRequestMiddleware::new( ledger_path, diff --git a/core/src/test_validator.rs b/core/src/test_validator.rs index f75b4eabfe..cdb7f11450 100644 --- a/core/src/test_validator.rs +++ b/core/src/test_validator.rs @@ -3,7 +3,7 @@ use { cluster_info::Node, gossip_service::discover_cluster, rpc::JsonRpcConfig, - validator::{Validator, ValidatorConfig}, + validator::{Validator, ValidatorConfig, ValidatorExit}, }, solana_client::rpc_client::RpcClient, solana_ledger::{blockstore::create_new_ledger, create_new_tmp_ledger}, @@ -28,7 +28,7 @@ use { fs::remove_dir_all, net::{IpAddr, Ipv4Addr, SocketAddr}, path::PathBuf, - sync::Arc, + sync::{Arc, RwLock}, thread::sleep, time::Duration, }, @@ -52,6 +52,7 @@ pub struct TestValidatorGenesis { no_bpf_jit: bool, accounts: HashMap, programs: Vec, + pub validator_exit: Arc>, } impl TestValidatorGenesis { @@ -401,6 +402,7 @@ impl TestValidator { enforce_ulimit_nofile: false, warp_slot: config.warp_slot, bpf_jit: !config.no_bpf_jit, + validator_exit: config.validator_exit.clone(), ..ValidatorConfig::default() }; @@ -417,7 +419,8 @@ impl TestValidator { // Needed to avoid panics in `solana-responder-gossip` in tests that create a number of // test validators concurrently... - discover_cluster(&gossip, 1).expect("TestValidator startup failed"); + discover_cluster(&gossip, 1) + .map_err(|err| format!("TestValidator startup failed: {:?}", err))?; // This is a hack to delay until the fees are non-zero for test consistency // (fees from genesis are zero until the first block with a transaction in it is completed @@ -425,19 +428,24 @@ impl TestValidator { { let rpc_client = RpcClient::new_with_commitment(rpc_url.clone(), CommitmentConfig::processed()); - let fee_rate_governor = rpc_client - .get_fee_rate_governor() - .expect("get_fee_rate_governor") - .value; - if fee_rate_governor.target_lamports_per_signature > 0 { - while rpc_client - .get_recent_blockhash() - .expect("get_recent_blockhash") - .1 - .lamports_per_signature - == 0 - { - sleep(Duration::from_millis(DEFAULT_MS_PER_SLOT)); + + if let Ok(result) = rpc_client.get_fee_rate_governor() { + let fee_rate_governor = result.value; + if fee_rate_governor.target_lamports_per_signature > 0 { + loop { + match rpc_client.get_recent_blockhash() { + Ok((_blockhash, fee_calculator)) => { + if fee_calculator.lamports_per_signature != 0 { + break; + } + } + Err(err) => { + warn!("get_recent_blockhash() failed: {:?}", err); + break; + } + } + sleep(Duration::from_millis(DEFAULT_MS_PER_SLOT)); + } } } } @@ -490,6 +498,12 @@ impl TestValidator { (rpc_client, recent_blockhash, fee_calculator) } + + pub fn join(mut self) { + if let Some(validator) = self.validator.take() { + validator.join(); + } + } } impl Drop for TestValidator { diff --git a/core/src/validator.rs b/core/src/validator.rs index 06d23c1d81..b32d7d35b8 100644 --- a/core/src/validator.rs +++ b/core/src/validator.rs @@ -188,15 +188,21 @@ impl Default for ValidatorConfig { #[derive(Default)] pub struct ValidatorExit { + exited: bool, exits: Vec>, } impl ValidatorExit { pub fn register_exit(&mut self, exit: Box) { - self.exits.push(exit); + if self.exited { + exit(); + } else { + self.exits.push(exit); + } } pub fn exit(&mut self) { + self.exited = true; for exit in self.exits.drain(..) { exit(); } @@ -219,16 +225,12 @@ struct TransactionHistoryServices { cache_block_time_service: Option, } -struct RpcServices { - json_rpc_service: JsonRpcService, - pubsub_service: PubSubService, - optimistically_confirmed_bank_tracker: OptimisticallyConfirmedBankTracker, -} - pub struct Validator { pub id: Pubkey, validator_exit: Arc>, - rpc_service: Option, + json_rpc_service: Option, + pubsub_service: Option, + optimistically_confirmed_bank_tracker: Option, transaction_status_service: Option, rewards_recorder_service: Option, cache_block_time_service: Option, @@ -475,9 +477,12 @@ impl Validator { let poh_recorder = Arc::new(Mutex::new(poh_recorder)); let rpc_override_health_check = Arc::new(AtomicBool::new(false)); - let (rpc_service, bank_notification_sender) = if let Some((rpc_addr, rpc_pubsub_addr)) = - config.rpc_addrs - { + let ( + json_rpc_service, + pubsub_service, + optimistically_confirmed_bank_tracker, + bank_notification_sender, + ) = if let Some((rpc_addr, rpc_pubsub_addr)) = config.rpc_addrs { if ContactInfo::is_valid_address(&node.info.rpc) { assert!(ContactInfo::is_valid_address(&node.info.rpc_pubsub)); } else { @@ -485,44 +490,46 @@ impl Validator { } let (bank_notification_sender, bank_notification_receiver) = unbounded(); ( - Some(RpcServices { - json_rpc_service: JsonRpcService::new( - rpc_addr, - config.rpc_config.clone(), - config.snapshot_config.clone(), - bank_forks.clone(), - block_commitment_cache.clone(), - blockstore.clone(), - cluster_info.clone(), - Some(poh_recorder.clone()), - genesis_config.hash(), - ledger_path, - config.validator_exit.clone(), - config.trusted_validators.clone(), - rpc_override_health_check.clone(), - optimistically_confirmed_bank.clone(), - config.send_transaction_retry_ms, - config.send_transaction_leader_forward_count, - max_slots.clone(), - ), - pubsub_service: PubSubService::new( + Some(JsonRpcService::new( + rpc_addr, + config.rpc_config.clone(), + config.snapshot_config.clone(), + bank_forks.clone(), + block_commitment_cache.clone(), + blockstore.clone(), + cluster_info.clone(), + Some(poh_recorder.clone()), + genesis_config.hash(), + ledger_path, + config.validator_exit.clone(), + config.trusted_validators.clone(), + rpc_override_health_check.clone(), + optimistically_confirmed_bank.clone(), + config.send_transaction_retry_ms, + config.send_transaction_leader_forward_count, + max_slots.clone(), + )), + if config.rpc_config.minimal_api { + None + } else { + Some(PubSubService::new( config.pubsub_config.clone(), &subscriptions, rpc_pubsub_addr, &exit, - ), - optimistically_confirmed_bank_tracker: OptimisticallyConfirmedBankTracker::new( - bank_notification_receiver, - &exit, - bank_forks.clone(), - optimistically_confirmed_bank, - subscriptions.clone(), - ), - }), + )) + }, + Some(OptimisticallyConfirmedBankTracker::new( + bank_notification_receiver, + &exit, + bank_forks.clone(), + optimistically_confirmed_bank, + subscriptions.clone(), + )), Some(bank_notification_sender), ) } else { - (None, None) + (None, None, None, None) }; if config.dev_halt_at_slot.is_some() { @@ -704,7 +711,9 @@ impl Validator { id, gossip_service, serve_repair_service, - rpc_service, + json_rpc_service, + pubsub_service, + optimistically_confirmed_bank_tracker, transaction_status_service, rewards_recorder_service, cache_block_time_service, @@ -758,18 +767,23 @@ impl Validator { pub fn join(self) { self.poh_service.join().expect("poh_service"); drop(self.poh_recorder); - if let Some(RpcServices { - json_rpc_service, - pubsub_service, - optimistically_confirmed_bank_tracker, - }) = self.rpc_service - { + + if let Some(json_rpc_service) = self.json_rpc_service { json_rpc_service.join().expect("rpc_service"); + } + + if let Some(pubsub_service) = self.pubsub_service { pubsub_service.join().expect("pubsub_service"); + } + + if let Some(optimistically_confirmed_bank_tracker) = + self.optimistically_confirmed_bank_tracker + { optimistically_confirmed_bank_tracker .join() .expect("optimistically_confirmed_bank_tracker"); } + if let Some(transaction_status_service) = self.transaction_status_service { transaction_status_service .join() diff --git a/docs/src/developing/clients/jsonrpc-api.md b/docs/src/developing/clients/jsonrpc-api.md index a53aca9b50..21a8224213 100644 --- a/docs/src/developing/clients/jsonrpc-api.md +++ b/docs/src/developing/clients/jsonrpc-api.md @@ -61,8 +61,6 @@ gives a convenient interface for the RPC methods. - [requestAirdrop](jsonrpc-api.md#requestairdrop) - [sendTransaction](jsonrpc-api.md#sendtransaction) - [simulateTransaction](jsonrpc-api.md#simulatetransaction) -- [setLogFilter](jsonrpc-api.md#setlogfilter) -- [validatorExit](jsonrpc-api.md#validatorexit) - [Subscription Websocket](jsonrpc-api.md#subscription-websocket) - [accountSubscribe](jsonrpc-api.md#accountsubscribe) - [accountUnsubscribe](jsonrpc-api.md#accountunsubscribe) @@ -2986,57 +2984,6 @@ Result: } ``` -### setLogFilter - -Sets the log filter on the validator - -#### Parameters: - -- `` - the new log filter to use - -#### Results: - -- `` - -#### Example: - -```bash -curl http://localhost:8899 -X POST -H "Content-Type: application/json" -d ' - {"jsonrpc":"2.0","id":1, "method":"setLogFilter", "params":["solana_core=debug"]} -' -``` - -Result: -```json -{"jsonrpc":"2.0","result":null,"id":1} -``` - -### validatorExit - -If a validator boots with RPC exit enabled (`--enable-rpc-exit` parameter), this request causes the validator to exit. - -#### Parameters: - -None - -#### Results: - -- `` - Whether the validator exit operation was successful - -#### Example: - -```bash -curl http://localhost:8899 -X POST -H "Content-Type: application/json" -d ' - {"jsonrpc":"2.0","id":1, "method":"validatorExit"} -' - -``` - -Result: -```json -{"jsonrpc":"2.0","result":true,"id":1} -``` - ## Subscription Websocket After connecting to the RPC PubSub websocket at `ws://
/`: diff --git a/docs/src/proposals/cluster-test-framework.md b/docs/src/proposals/cluster-test-framework.md index 70717e9ff8..fe3a46c63e 100644 --- a/docs/src/proposals/cluster-test-framework.md +++ b/docs/src/proposals/cluster-test-framework.md @@ -51,7 +51,6 @@ For example: ```text let mut validator_config = ValidatorConfig::default(); -validator_config.rpc_config.enable_validator_exit = true; let local = LocalCluster::new_with_config( num_nodes, 10_000, diff --git a/gossip/Cargo.toml b/gossip/Cargo.toml index 16bf91a08c..1768e4bb10 100644 --- a/gossip/Cargo.toml +++ b/gossip/Cargo.toml @@ -12,7 +12,6 @@ homepage = "https://solana.com/" clap = "2.33.1" solana-clap-utils = { path = "../clap-utils", version = "1.6.0" } solana-core = { path = "../core", version = "1.6.0" } -solana-client = { path = "../client", version = "1.6.0" } solana-logger = { path = "../logger", version = "1.6.0" } solana-net-utils = { path = "../net-utils", version = "1.6.0" } solana-sdk = { path = "../sdk", version = "1.6.0" } diff --git a/gossip/src/main.rs b/gossip/src/main.rs index 716334dd2d..c440b8f281 100644 --- a/gossip/src/main.rs +++ b/gossip/src/main.rs @@ -8,7 +8,6 @@ use solana_clap_utils::{ input_parsers::keypair_of, input_validators::{is_keypair_or_ask_keyword, is_port, is_pubkey}, }; -use solana_client::rpc_client::RpcClient; use solana_core::{contact_info::ContactInfo, gossip_service::discover}; use solana_sdk::pubkey::Pubkey; use std::{ @@ -141,29 +140,6 @@ fn parse_matches() -> ArgMatches<'static> { .help("Maximum time to wait in seconds [default: wait forever]"), ), ) - .subcommand( - SubCommand::with_name("stop") - .about("Send stop request to a node") - .setting(AppSettings::DisableVersion) - .arg( - Arg::with_name("entrypoint") - .short("n") - .long("entrypoint") - .value_name("HOST:PORT") - .takes_value(true) - .required(true) - .validator(solana_net_utils::is_host_port) - .help("Rendezvous with the cluster at this entry point"), - ) - .arg( - Arg::with_name("node_pubkey") - .index(1) - .required(true) - .value_name("PUBKEY") - .validator(is_pubkey) - .help("Public key of a specific node to stop"), - ), - ) .get_matches() } @@ -332,44 +308,6 @@ fn process_rpc_url(matches: &ArgMatches) -> std::io::Result<()> { Ok(()) } -fn process_stop(matches: &ArgMatches) -> Result<(), Box> { - let entrypoint_addr = parse_entrypoint(&matches); - let pubkey = matches - .value_of("node_pubkey") - .unwrap() - .parse::() - .unwrap(); - let (_all_peers, validators) = discover( - None, - entrypoint_addr.as_ref(), - None, - None, - Some(pubkey), - None, - None, - 0, - )?; - let validator = validators.iter().find(|x| x.id == pubkey).unwrap(); - - if !ContactInfo::is_valid_address(&validator.rpc) { - eprintln!( - "Error: RPC service is not enabled on validator {:?}", - pubkey - ); - exit(1); - } - println!("\nSending stop request to validator {:?}", pubkey); - - let result = RpcClient::new_socket(validator.rpc).validator_exit()?; - if result { - println!("Stop signal accepted"); - } else { - eprintln!("Error: Stop signal ignored"); - } - - Ok(()) -} - fn main() -> Result<(), Box> { solana_logger::setup_with_default("solana=info"); @@ -382,9 +320,6 @@ fn main() -> Result<(), Box> { ("rpc-url", Some(matches)) => { process_rpc_url(matches)?; } - ("stop", Some(matches)) => { - process_stop(matches)?; - } _ => unreachable!(), } diff --git a/local-cluster/src/cluster_tests.rs b/local-cluster/src/cluster_tests.rs index 1e2e892bd2..74d1151bb9 100644 --- a/local-cluster/src/cluster_tests.rs +++ b/local-cluster/src/cluster_tests.rs @@ -5,6 +5,7 @@ use log::*; /// discover the rest of the network. use rand::{thread_rng, Rng}; use solana_client::thin_client::create_client; +use solana_core::validator::ValidatorExit; use solana_core::{ cluster_info::VALIDATOR_PORT_RANGE, consensus::VOTE_THRESHOLD_DEPTH, contact_info::ContactInfo, gossip_service::discover_cluster, @@ -15,7 +16,7 @@ use solana_ledger::{ }; use solana_sdk::{ client::SyncClient, - clock::{self, Slot, DEFAULT_MS_PER_SLOT, NUM_CONSECUTIVE_LEADER_SLOTS}, + clock::{self, Slot, NUM_CONSECUTIVE_LEADER_SLOTS}, commitment_config::CommitmentConfig, epoch_schedule::MINIMUM_SLOTS_PER_EPOCH, hash::Hash, @@ -29,6 +30,7 @@ use solana_sdk::{ use std::{ collections::{HashMap, HashSet}, path::Path, + sync::{Arc, RwLock}, thread::sleep, time::{Duration, Instant}, }; @@ -126,20 +128,6 @@ pub fn send_many_transactions( expected_balances } -pub fn validator_exit(entry_point_info: &ContactInfo, nodes: usize) { - let cluster_nodes = discover_cluster(&entry_point_info.gossip, nodes).unwrap(); - assert!(cluster_nodes.len() >= nodes); - for node in &cluster_nodes { - let client = create_client(node.client_facing_addr(), VALIDATOR_PORT_RANGE); - assert!(client.validator_exit().unwrap()); - } - sleep(Duration::from_millis(DEFAULT_MS_PER_SLOT)); - for node in &cluster_nodes { - let client = create_client(node.client_facing_addr(), VALIDATOR_PORT_RANGE); - assert!(client.validator_exit().is_err()); - } -} - pub fn verify_ledger_ticks(ledger_path: &Path, ticks_per_slot: usize) { let ledger = Blockstore::open(ledger_path).unwrap(); let zeroth_slot = ledger.get_slot_entries(0, 0).unwrap(); @@ -188,11 +176,12 @@ pub fn sleep_n_epochs( pub fn kill_entry_and_spend_and_verify_rest( entry_point_info: &ContactInfo, + entry_point_validator_exit: &Arc>, funding_keypair: &Keypair, nodes: usize, slot_millis: u64, ) { - solana_logger::setup(); + info!("kill_entry_and_spend_and_verify_rest..."); let cluster_nodes = discover_cluster(&entry_point_info.gossip, nodes).unwrap(); assert!(cluster_nodes.len() >= nodes); let client = create_client(entry_point_info.client_facing_addr(), VALIDATOR_PORT_RANGE); @@ -211,7 +200,7 @@ pub fn kill_entry_and_spend_and_verify_rest( )); info!("done sleeping for first 2 warmup epochs"); info!("killing entry point: {}", entry_point_info.id); - assert!(client.validator_exit().unwrap()); + entry_point_validator_exit.write().unwrap().exit(); info!("sleeping for some time"); sleep(Duration::from_millis( slot_millis * NUM_CONSECUTIVE_LEADER_SLOTS, diff --git a/local-cluster/src/local_cluster.rs b/local-cluster/src/local_cluster.rs index 93c078b0a3..dfc0c01342 100644 --- a/local-cluster/src/local_cluster.rs +++ b/local-cluster/src/local_cluster.rs @@ -667,7 +667,7 @@ impl Cluster for LocalCluster { entry_point_info .map(|entry_point_info| vec![entry_point_info]) .unwrap_or_default(), - &cluster_validator_info.config, + &safe_clone_config(&cluster_validator_info.config), true, // should_check_duplicate_instance ); cluster_validator_info.validator = Some(restarted_node); @@ -707,8 +707,6 @@ mod test { #[test] fn test_local_cluster_start_and_exit_with_config() { solana_logger::setup(); - let mut validator_config = ValidatorConfig::default(); - validator_config.rpc_config.enable_validator_exit = true; const NUM_NODES: usize = 1; let mut config = ClusterConfig { validator_configs: make_identical_validator_configs( diff --git a/local-cluster/tests/local_cluster.rs b/local-cluster/tests/local_cluster.rs index ef9e13c302..3246a3fe9c 100644 --- a/local-cluster/tests/local_cluster.rs +++ b/local-cluster/tests/local_cluster.rs @@ -241,37 +241,6 @@ fn test_spend_and_verify_all_nodes_env_num_nodes() { ); } -#[allow(unused_attributes)] -#[test] -#[should_panic] -fn test_validator_exit_default_config_should_panic() { - solana_logger::setup(); - error!("test_validator_exit_default_config_should_panic"); - let num_nodes = 2; - let local = LocalCluster::new_with_equal_stakes(num_nodes, 10_000, 100); - cluster_tests::validator_exit(&local.entry_point_info, num_nodes); -} - -#[test] -#[serial] -fn test_validator_exit_2() { - solana_logger::setup(); - error!("test_validator_exit_2"); - let num_nodes = 2; - let mut validator_config = ValidatorConfig::default(); - validator_config.rpc_config.enable_validator_exit = true; - validator_config.wait_for_supermajority = Some(0); - - let mut config = ClusterConfig { - cluster_lamports: 10_000, - node_stakes: vec![100; num_nodes], - validator_configs: make_identical_validator_configs(&validator_config, num_nodes), - ..ClusterConfig::default() - }; - let local = LocalCluster::new(&mut config); - cluster_tests::validator_exit(&local.entry_point_info, num_nodes); -} - // Cluster needs a supermajority to remain, so the minimum size for this test is 4 #[test] #[serial] @@ -279,8 +248,7 @@ fn test_leader_failure_4() { solana_logger::setup(); error!("test_leader_failure_4"); let num_nodes = 4; - let mut validator_config = ValidatorConfig::default(); - validator_config.rpc_config.enable_validator_exit = true; + let validator_config = ValidatorConfig::default(); let mut config = ClusterConfig { cluster_lamports: 10_000, node_stakes: vec![100; 4], @@ -288,8 +256,15 @@ fn test_leader_failure_4() { ..ClusterConfig::default() }; let local = LocalCluster::new(&mut config); + cluster_tests::kill_entry_and_spend_and_verify_rest( &local.entry_point_info, + &local + .validators + .get(&local.entry_point_info.id) + .unwrap() + .config + .validator_exit, &local.funding_keypair, num_nodes, config.ticks_per_slot * config.poh_config.target_tick_duration.as_millis() as u64, @@ -647,12 +622,11 @@ fn test_kill_partition_switch_threshold_progress() { fn test_two_unbalanced_stakes() { solana_logger::setup(); error!("test_two_unbalanced_stakes"); - let mut validator_config = ValidatorConfig::default(); + let validator_config = ValidatorConfig::default(); let num_ticks_per_second = 100; let num_ticks_per_slot = 10; let num_slots_per_epoch = MINIMUM_SLOTS_PER_EPOCH as u64; - validator_config.rpc_config.enable_validator_exit = true; let mut cluster = LocalCluster::new(&mut ClusterConfig { node_stakes: vec![999_990, 3], cluster_lamports: 1_000_000, @@ -1388,8 +1362,7 @@ fn test_faulty_node(faulty_node_type: BroadcastStageType) { #[test] fn test_wait_for_max_stake() { solana_logger::setup(); - let mut validator_config = ValidatorConfig::default(); - validator_config.rpc_config.enable_validator_exit = true; + let validator_config = ValidatorConfig::default(); let mut config = ClusterConfig { cluster_lamports: 10_000, node_stakes: vec![100; 4], @@ -1410,9 +1383,10 @@ fn test_wait_for_max_stake() { // votable, then B_{i+1} still chains to B_i fn test_no_voting() { solana_logger::setup(); - let mut validator_config = ValidatorConfig::default(); - validator_config.rpc_config.enable_validator_exit = true; - validator_config.voting_disabled = true; + let validator_config = ValidatorConfig { + voting_disabled: true, + ..ValidatorConfig::default() + }; let mut config = ClusterConfig { cluster_lamports: 10_000, node_stakes: vec![100], @@ -2498,11 +2472,12 @@ fn setup_snapshot_validator_config( let (account_storage_dirs, account_storage_paths) = generate_account_paths(num_account_paths); // Create the validator config - let mut validator_config = ValidatorConfig::default(); - validator_config.rpc_config.enable_validator_exit = true; - validator_config.snapshot_config = Some(snapshot_config); - validator_config.account_paths = account_storage_paths; - validator_config.accounts_hash_interval_slots = snapshot_interval_slots; + let validator_config = ValidatorConfig { + snapshot_config: Some(snapshot_config), + account_paths: account_storage_paths, + accounts_hash_interval_slots: snapshot_interval_slots, + ..ValidatorConfig::default() + }; SnapshotValidatorConfig { _snapshot_dir: snapshot_dir, diff --git a/multinode-demo/bootstrap-validator.sh b/multinode-demo/bootstrap-validator.sh index 426f6c76e4..9cbdecadd7 100755 --- a/multinode-demo/bootstrap-validator.sh +++ b/multinode-demo/bootstrap-validator.sh @@ -97,8 +97,6 @@ ledger_dir="$SOLANA_CONFIG_DIR"/bootstrap-validator } args+=( - --enable-rpc-exit - --enable-rpc-set-log-filter --require-tower --ledger "$ledger_dir" --rpc-port 8899 diff --git a/multinode-demo/validator.sh b/multinode-demo/validator.sh index 253e583b6a..b58485be56 100755 --- a/multinode-demo/validator.sh +++ b/multinode-demo/validator.sh @@ -104,9 +104,6 @@ while [[ -n $1 ]]; do elif [[ $1 = --rpc-port ]]; then args+=("$1" "$2") shift 2 - elif [[ $1 = --enable-rpc-exit ]]; then - args+=("$1") - shift elif [[ $1 = --rpc-faucet-address ]]; then args+=("$1" "$2") shift 2 @@ -227,8 +224,6 @@ default_arg --identity "$identity" default_arg --vote-account "$vote_account" default_arg --ledger "$ledger_dir" default_arg --log - -default_arg --enable-rpc-exit -default_arg --enable-rpc-set-log-filter default_arg --require-tower if [[ -n $SOLANA_CUDA ]]; then diff --git a/run.sh b/run.sh index 6e503b458d..f8979bad9e 100755 --- a/run.sh +++ b/run.sh @@ -100,7 +100,6 @@ args=( --rpc-port 8899 --rpc-faucet-address 127.0.0.1:9900 --log - - --enable-rpc-exit --enable-rpc-transaction-history --enable-cpi-and-log-storage --init-complete-file "$dataDir"/init-completed diff --git a/scripts/set-log-filter.sh b/scripts/set-log-filter.sh deleted file mode 100755 index bee721e019..0000000000 --- a/scripts/set-log-filter.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash -# -# Reconfigures the log filter on a validator using the current RUST_LOG value -# - -if [[ -n $1 ]]; then - url=$1 -else - # Default to the local node - url=http://127.0.0.1:8899 -fi - -if [[ -z $RUST_LOG ]]; then - echo "RUST_LOG not defined" - exit 1 -fi - -set -x -exec curl $url -X POST -H "Content-Type: application/json" \ - -d " - { - \"jsonrpc\": \"2.0\", - \"id\": 1, - \"method\": \"setLogFilter\", - \"params\": [\"$RUST_LOG\"] - } - " diff --git a/validator/Cargo.toml b/validator/Cargo.toml index 727edc60c3..450542a219 100644 --- a/validator/Cargo.toml +++ b/validator/Cargo.toml @@ -12,18 +12,21 @@ default-run = "solana-validator" [dependencies] base64 = "0.12.3" bincode = "1.3.1" -clap = "2.33.1" chrono = { version = "0.4.11", features = ["serde"] } +clap = "2.33.1" console = "0.11.3" core_affinity = "0.5.10" fd-lock = "1.1.1" indicatif = "0.15.0" +jsonrpc-core = "17.0.0" +jsonrpc-core-client = { version = "17.0.0", features = ["ipc", "ws"] } +jsonrpc-derive = "17.0.0" +jsonrpc-ipc-server = "17.0.0" +jsonrpc-server-utils= "17.0.0" log = "0.4.11" num_cpus = "1.13.0" rand = "0.7.0" serde = "1.0.112" -serde_derive = "1.0.103" -serde_yaml = "0.8.13" solana-clap-utils = { path = "../clap-utils", version = "1.6.0" } solana-cli-config = { path = "../cli-config", version = "1.6.0" } solana-client = { path = "../client", version = "1.6.0" } @@ -32,9 +35,9 @@ solana-download-utils = { path = "../download-utils", version = "1.6.0" } solana-faucet = { path = "../faucet", version = "1.6.0" } solana-ledger = { path = "../ledger", version = "1.6.0" } solana-logger = { path = "../logger", version = "1.6.0" } -solana-perf = { path = "../perf", version = "1.6.0" } solana-metrics = { path = "../metrics", version = "1.6.0" } solana-net-utils = { path = "../net-utils", version = "1.6.0" } +solana-perf = { path = "../perf", version = "1.6.0" } solana-runtime = { path = "../runtime", version = "1.6.0" } solana-sdk = { path = "../sdk", version = "1.6.0" } solana-version = { path = "../version", version = "1.6.0" } diff --git a/validator/src/admin_rpc_service.rs b/validator/src/admin_rpc_service.rs new file mode 100644 index 0000000000..476013b3f9 --- /dev/null +++ b/validator/src/admin_rpc_service.rs @@ -0,0 +1,133 @@ +use { + jsonrpc_core::{MetaIoHandler, Metadata, Result}, + jsonrpc_core_client::{transports::ipc, RpcError}, + jsonrpc_derive::rpc, + jsonrpc_ipc_server::{RequestContext, ServerBuilder}, + jsonrpc_server_utils::tokio, + log::*, + solana_core::validator::ValidatorExit, + std::{ + net::SocketAddr, + path::Path, + sync::{Arc, RwLock}, + thread::Builder, + time::SystemTime, + }, +}; + +#[derive(Clone)] +pub struct AdminRpcRequestMetadata { + pub rpc_addr: Option, + pub start_time: SystemTime, + pub validator_exit: Arc>, +} +impl Metadata for AdminRpcRequestMetadata {} + +#[rpc] +pub trait AdminRpc { + type Metadata; + + #[rpc(meta, name = "exit")] + fn exit(&self, meta: Self::Metadata) -> Result<()>; + + #[rpc(meta, name = "rpcAddress")] + fn rpc_addr(&self, meta: Self::Metadata) -> Result>; + + #[rpc(name = "setLogFilter")] + fn set_log_filter(&self, filter: String) -> Result<()>; + + #[rpc(meta, name = "startTime")] + fn start_time(&self, meta: Self::Metadata) -> Result; +} + +pub struct AdminRpcImpl; +impl AdminRpc for AdminRpcImpl { + type Metadata = AdminRpcRequestMetadata; + + fn exit(&self, meta: Self::Metadata) -> Result<()> { + info!("exit admin rpc request received"); + // Delay exit signal until this RPC request completes, otherwise the caller of `exit` might + // receive a confusing error as the validator shuts down before a response is send back. + tokio::spawn(async move { + meta.validator_exit.write().unwrap().exit(); + }); + Ok(()) + } + + fn rpc_addr(&self, meta: Self::Metadata) -> Result> { + info!("rpc_addr admin rpc request received"); + Ok(meta.rpc_addr) + } + + fn set_log_filter(&self, filter: String) -> Result<()> { + info!("set_log_filter admin rpc request received"); + solana_logger::setup_with(&filter); + Ok(()) + } + + fn start_time(&self, meta: Self::Metadata) -> Result { + info!("start_time admin rpc request received"); + Ok(meta.start_time) + } +} + +// Start the Admin RPC interface +pub fn run(ledger_path: &Path, metadata: AdminRpcRequestMetadata) { + let admin_rpc_path = ledger_path.join("admin.rpc"); + + let event_loop = tokio::runtime::Builder::new() + .threaded_scheduler() + .enable_all() + .thread_name("sol-adminrpc-el") + .build() + .unwrap(); + + Builder::new() + .name("solana-adminrpc".to_string()) + .spawn(move || { + let mut io = MetaIoHandler::default(); + io.extend_with(AdminRpcImpl.to_delegate()); + + let validator_exit = metadata.validator_exit.clone(); + let server = ServerBuilder::with_meta_extractor(io, move |_req: &RequestContext| { + metadata.clone() + }) + .event_loop_executor(event_loop.handle().clone()) + .start(&format!("{}", admin_rpc_path.display())); + + match server { + Err(err) => { + warn!("Unable to start admin rpc service: {:?}", err); + } + Ok(server) => { + let close_handle = server.close_handle(); + validator_exit + .write() + .unwrap() + .register_exit(Box::new(move || { + close_handle.close(); + })); + + server.wait(); + } + } + }) + .unwrap(); +} + +// Connect to the Admin RPC interface +pub async fn connect(ledger_path: &Path) -> std::result::Result { + let admin_rpc_path = ledger_path.join("admin.rpc"); + if !admin_rpc_path.exists() { + Err(RpcError::Client(format!( + "{} does not exist", + admin_rpc_path.display() + ))) + } else { + ipc::connect::<_, gen_client::Client>(&format!("{}", admin_rpc_path.display())).await + } +} + +pub fn runtime() -> jsonrpc_server_utils::tokio::runtime::Runtime { + jsonrpc_server_utils::tokio::runtime::Runtime::new().expect("new tokio runtime") +} diff --git a/validator/src/bin/solana-test-validator.rs b/validator/src/bin/solana-test-validator.rs index 06d6bf487c..947465665a 100644 --- a/validator/src/bin/solana-test-validator.rs +++ b/validator/src/bin/solana-test-validator.rs @@ -21,7 +21,7 @@ use { system_program, }, solana_validator::{ - dashboard::Dashboard, record_start, redirect_stderr_to_file, test_validator::*, + admin_rpc_service, dashboard::Dashboard, redirect_stderr_to_file, test_validator::*, }, std::{ collections::HashSet, @@ -126,7 +126,7 @@ fn main() { .takes_value(true) .default_value(&default_rpc_port) .validator(solana_validator::port_validator) - .help("Use this port for JSON RPC and the next port for the RPC websocket"), + .help("Enable JSON RPC on this port, and the next port for the RPC websocket"), ) .arg( Arg::with_name("bpf_program") @@ -205,6 +205,7 @@ fn main() { Output::Dashboard }; let rpc_port = value_t_or_exit!(matches, "rpc_port", u16); + let faucet_addr = Some(SocketAddr::new( IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), FAUCET_PORT, @@ -353,60 +354,68 @@ fn main() { }); } - record_start( - &ledger_path, - Some(&SocketAddr::new( - IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), - rpc_port, - )), - ) - .unwrap_or_else(|err| println!("Error: failed to record validator start: {}", err)); + let mut genesis = TestValidatorGenesis::default(); + admin_rpc_service::run( + &ledger_path, + admin_rpc_service::AdminRpcRequestMetadata { + rpc_addr: Some(SocketAddr::new( + IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + rpc_port, + )), + start_time: std::time::SystemTime::now(), + validator_exit: genesis.validator_exit.clone(), + }, + ); let dashboard = if output == Output::Dashboard { - Some(Dashboard::new(&ledger_path, Some(&validator_log_symlink)).unwrap()) + Some( + Dashboard::new( + &ledger_path, + Some(&validator_log_symlink), + Some(&mut genesis.validator_exit.write().unwrap()), + ) + .unwrap(), + ) } else { None }; - let test_validator = { - let mut genesis = TestValidatorGenesis::default(); - genesis - .ledger_path(&ledger_path) - .add_account( - faucet_pubkey, - Account::new(faucet_lamports, 0, &system_program::id()), - ) - .rpc_config(JsonRpcConfig { - enable_validator_exit: true, - enable_rpc_transaction_history: true, - enable_cpi_and_log_storage: true, - faucet_addr, - ..JsonRpcConfig::default() - }) - .bpf_jit(bpf_jit) - .rpc_port(rpc_port) - .add_programs_with_path(&programs); + genesis + .ledger_path(&ledger_path) + .add_account( + faucet_pubkey, + Account::new(faucet_lamports, 0, &system_program::id()), + ) + .rpc_config(JsonRpcConfig { + enable_rpc_transaction_history: true, + enable_cpi_and_log_storage: true, + faucet_addr, + ..JsonRpcConfig::default() + }) + .bpf_jit(bpf_jit) + .rpc_port(rpc_port) + .add_programs_with_path(&programs); - if !clone_accounts.is_empty() { - genesis.clone_accounts( - clone_accounts, - cluster_rpc_client - .as_ref() - .expect("bug: --url argument missing?"), - ); + if !clone_accounts.is_empty() { + genesis.clone_accounts( + clone_accounts, + cluster_rpc_client + .as_ref() + .expect("bug: --url argument missing?"), + ); + } + + if let Some(warp_slot) = warp_slot { + genesis.warp_slot(warp_slot); + } + + match genesis.start_with_mint_address(mint_address) { + Ok(test_validator) => { + if let Some(dashboard) = dashboard { + dashboard.run(); + } + test_validator.join(); } - - if let Some(warp_slot) = warp_slot { - genesis.warp_slot(warp_slot); - } - genesis.start_with_mint_address(mint_address) - }; - - match test_validator { - Ok(_test_validator) => match dashboard { - Some(dashboard) => dashboard.run(), - None => std::thread::park(), - }, Err(err) => { drop(dashboard); println!("Error: failed to start validator: {}", err); @@ -418,10 +427,10 @@ fn main() { fn remove_directory_contents(ledger_path: &Path) -> Result<(), io::Error> { for entry in fs::read_dir(&ledger_path)? { let entry = entry?; - if entry.metadata()?.is_file() { - fs::remove_file(&entry.path())? - } else { + if entry.metadata()?.is_dir() { fs::remove_dir_all(&entry.path())? + } else { + fs::remove_file(&entry.path())? } } Ok(()) diff --git a/validator/src/dashboard.rs b/validator/src/dashboard.rs index 276fb717fa..688f8e32e2 100644 --- a/validator/src/dashboard.rs +++ b/validator/src/dashboard.rs @@ -1,8 +1,5 @@ use { - crate::{ - get_validator_rpc_addr, get_validator_start_time, new_spinner_progress_bar, - println_name_value, - }, + crate::{admin_rpc_service, new_spinner_progress_bar, println_name_value}, console::style, indicatif::ProgressBar, solana_client::{ @@ -17,6 +14,10 @@ use { std::{ io, path::{Path, PathBuf}, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, thread, time::Duration, }, @@ -25,136 +26,121 @@ use { pub struct Dashboard { progress_bar: ProgressBar, ledger_path: PathBuf, + exit: Arc, } impl Dashboard { - pub fn new(ledger_path: &Path, log_path: Option<&Path>) -> Result { + pub fn new( + ledger_path: &Path, + log_path: Option<&Path>, + validator_exit: Option<&mut solana_core::validator::ValidatorExit>, + ) -> Result { println_name_value("Ledger location:", &format!("{}", ledger_path.display())); if let Some(log_path) = log_path { println_name_value("Log:", &format!("{}", log_path.display())); } - let rpc_addr = get_validator_rpc_addr(&ledger_path)?; - if rpc_addr.is_none() { - return Err(io::Error::new(io::ErrorKind::Other, "RPC not available")); - } - let progress_bar = new_spinner_progress_bar(); progress_bar.set_message("Initializing..."); + let exit = Arc::new(AtomicBool::new(false)); + if let Some(validator_exit) = validator_exit { + let exit = exit.clone(); + validator_exit.register_exit(Box::new(move || exit.store(true, Ordering::Relaxed))); + } + Ok(Self { - progress_bar, + exit, ledger_path: ledger_path.to_path_buf(), + progress_bar, }) } - pub fn run(self) -> ! { + pub fn run(self) { let Self { - progress_bar, + exit, ledger_path, + progress_bar, + .. } = self; - - progress_bar.set_message("Connecting..."); - - let rpc_addr = get_validator_rpc_addr(&ledger_path).unwrap().unwrap(); - let rpc_client = RpcClient::new_socket(rpc_addr); - - // Wait until RPC starts responding... - loop { - match rpc_client.get_identity() { - Ok(_) => break, - Err(err) => { - progress_bar.set_message(&format!("{}", err)); - thread::sleep(Duration::from_millis(500)); - } - } - } - drop(progress_bar); - let identity = &rpc_client.get_identity().expect("get_identity"); + let mut runtime = admin_rpc_service::runtime(); + while !exit.load(Ordering::Relaxed) { + let progress_bar = new_spinner_progress_bar(); + progress_bar.set_message("Connecting..."); + let (start_time, rpc_client, identity) = loop { + if exit.load(Ordering::Relaxed) { + return; + } - println_name_value("Identity:", &identity.to_string()); + let admin_client = admin_rpc_service::connect(&ledger_path); + let (rpc_addr, start_time) = match runtime.block_on(async move { + let admin_client = admin_client.await.map_err(|err| { + format!("Unable to connect to validator process: {}", err) + })?; - fn get_contact_info(rpc_client: &RpcClient, identity: &Pubkey) -> Option { - rpc_client - .get_cluster_nodes() - .ok() - .unwrap_or_default() - .into_iter() - .find(|node| node.pubkey == identity.to_string()) - } + let rpc_addr = admin_client + .rpc_addr() + .await + .map_err(|err| format!("Unable to get validator RPC address: {}", err))? + .ok_or_else(|| "RPC not available".to_string())?; - if let Some(contact_info) = get_contact_info(&rpc_client, &identity) { - println_name_value( - "Version:", - &contact_info.version.unwrap_or_else(|| "?".to_string()), - ); - if let Some(gossip) = contact_info.gossip { - println_name_value("Gossip Address:", &gossip.to_string()); - } - if let Some(tpu) = contact_info.tpu { - println_name_value("TPU Address:", &tpu.to_string()); - } - if let Some(rpc) = contact_info.rpc { - println_name_value("JSON RPC URL:", &format!("http://{}", rpc.to_string())); - } - } + let start_time = admin_client + .start_time() + .await + .map_err(|err| format!("Unable to get validator start time: {}", err))?; - let progress_bar = new_spinner_progress_bar(); + Ok::<_, String>((rpc_addr, start_time)) + }) { + Ok((rpc_addr, start_time)) => (rpc_addr, start_time), + Err(err) => { + progress_bar.set_message(&format!("Connecting... ({})", err)); + thread::sleep(Duration::from_millis(500)); + continue; + } + }; - fn get_validator_stats( - rpc_client: &RpcClient, - identity: &Pubkey, - ) -> client_error::Result<(Slot, Slot, Slot, u64, Sol, String)> { - let processed_slot = - rpc_client.get_slot_with_commitment(CommitmentConfig::processed())?; - let confirmed_slot = - rpc_client.get_slot_with_commitment(CommitmentConfig::confirmed())?; - let finalized_slot = - rpc_client.get_slot_with_commitment(CommitmentConfig::finalized())?; - let transaction_count = - rpc_client.get_transaction_count_with_commitment(CommitmentConfig::processed())?; - let identity_balance = rpc_client - .get_balance_with_commitment(identity, CommitmentConfig::confirmed())? - .value; + let rpc_client = RpcClient::new_socket(rpc_addr); - let health = match rpc_client.get_health() { - Ok(()) => "ok".to_string(), - Err(err) => { - if let client_error::ClientErrorKind::RpcError( - rpc_request::RpcError::RpcResponseError { - code: _, - message: _, - data: - rpc_request::RpcResponseErrorData::NodeUnhealthy { - num_slots_behind: Some(num_slots_behind), - }, - }, - ) = &err.kind - { - format!("{} slots behind", num_slots_behind) - } else { - "unhealthy".to_string() + // Wait until RPC starts responding... + match rpc_client.get_identity() { + Ok(identity) => break (start_time, rpc_client, identity), + Err(err) => { + progress_bar.set_message(&format!("Waiting for RPC... ({})", err)); } } }; - Ok(( - processed_slot, - confirmed_slot, - finalized_slot, - transaction_count, - Sol(identity_balance), - health, - )) - } + drop(progress_bar); + println_name_value("Identity:", &identity.to_string()); - let mut start_time = get_validator_start_time(&ledger_path).ok(); - loop { - let snapshot_slot = rpc_client.get_snapshot_slot().ok(); + if let Some(contact_info) = get_contact_info(&rpc_client, &identity) { + println_name_value( + "Version:", + &contact_info.version.unwrap_or_else(|| "?".to_string()), + ); + if let Some(gossip) = contact_info.gossip { + println_name_value("Gossip Address:", &gossip.to_string()); + } + if let Some(tpu) = contact_info.tpu { + println_name_value("TPU Address:", &tpu.to_string()); + } + if let Some(rpc) = contact_info.rpc { + println_name_value("JSON RPC URL:", &format!("http://{}", rpc.to_string())); + } + } + + let progress_bar = new_spinner_progress_bar(); + let mut snapshot_slot = None; + for i in 0.. { + if exit.load(Ordering::Relaxed) { + break; + } + if i % 10 == 0 { + snapshot_slot = rpc_client.get_snapshot_slot().ok(); + } - for _i in 0..10 { match get_validator_stats(&rpc_client, &identity) { Ok(( processed_slot, @@ -164,27 +150,23 @@ impl Dashboard { identity_balance, health, )) => { - let uptime = match start_time { - Some(start_time) => { - let uptime = - chrono::Duration::from_std(start_time.elapsed().unwrap()) - .unwrap(); + let uptime = { + let uptime = + chrono::Duration::from_std(start_time.elapsed().unwrap()).unwrap(); - format!( - "{:02}:{:02}:{:02} ", - uptime.num_hours(), - uptime.num_minutes() % 60, - uptime.num_seconds() % 60 - ) - } - None => " ".to_string(), + format!( + "{:02}:{:02}:{:02} ", + uptime.num_hours(), + uptime.num_minutes() % 60, + uptime.num_seconds() % 60 + ) }; progress_bar.set_message(&format!( "{}{}| \ - Processed Slot: {} | Confirmed Slot: {} | Finalized Slot: {} | \ - Snapshot Slot: {} | \ - Transactions: {} | {}", + Processed Slot: {} | Confirmed Slot: {} | Finalized Slot: {} | \ + Snapshot Slot: {} | \ + Transactions: {} | {}", uptime, if health == "ok" { "".to_string() @@ -200,16 +182,70 @@ impl Dashboard { transaction_count, identity_balance )); + thread::sleep(Duration::from_millis( + MS_PER_TICK * DEFAULT_TICKS_PER_SLOT / 2, + )); } Err(err) => { - start_time = get_validator_start_time(&ledger_path).ok(); - progress_bar.set_message(&format!("{}", err)); + progress_bar + .abandon_with_message(&format!("RPC connection failure: {}", err)); + break; } } - thread::sleep(Duration::from_millis( - MS_PER_TICK * DEFAULT_TICKS_PER_SLOT / 2, - )); } } } } + +fn get_contact_info(rpc_client: &RpcClient, identity: &Pubkey) -> Option { + rpc_client + .get_cluster_nodes() + .ok() + .unwrap_or_default() + .into_iter() + .find(|node| node.pubkey == identity.to_string()) +} + +fn get_validator_stats( + rpc_client: &RpcClient, + identity: &Pubkey, +) -> client_error::Result<(Slot, Slot, Slot, u64, Sol, String)> { + let processed_slot = rpc_client.get_slot_with_commitment(CommitmentConfig::processed())?; + let confirmed_slot = rpc_client.get_slot_with_commitment(CommitmentConfig::confirmed())?; + let finalized_slot = rpc_client.get_slot_with_commitment(CommitmentConfig::finalized())?; + let transaction_count = + rpc_client.get_transaction_count_with_commitment(CommitmentConfig::processed())?; + let identity_balance = rpc_client + .get_balance_with_commitment(identity, CommitmentConfig::confirmed())? + .value; + + let health = match rpc_client.get_health() { + Ok(()) => "ok".to_string(), + Err(err) => { + if let client_error::ClientErrorKind::RpcError( + rpc_request::RpcError::RpcResponseError { + code: _, + message: _, + data: + rpc_request::RpcResponseErrorData::NodeUnhealthy { + num_slots_behind: Some(num_slots_behind), + }, + }, + ) = &err.kind + { + format!("{} slots behind", num_slots_behind) + } else { + "unhealthy".to_string() + } + } + }; + + Ok(( + processed_slot, + confirmed_slot, + finalized_slot, + transaction_count, + Sol(identity_balance), + health, + )) +} diff --git a/validator/src/lib.rs b/validator/src/lib.rs index 88c1a4339a..76cf101bd5 100644 --- a/validator/src/lib.rs +++ b/validator/src/lib.rs @@ -4,19 +4,10 @@ use { console::style, indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle}, log::*, - serde_derive::{Deserialize, Serialize}, - std::{ - env, - fs::{self, File}, - io::{self, Write}, - net::SocketAddr, - path::Path, - process::exit, - thread::JoinHandle, - time::{Duration, SystemTime}, - }, + std::{env, process::exit, thread::JoinHandle}, }; +pub mod admin_rpc_service; pub mod dashboard; #[cfg(unix)] @@ -91,52 +82,6 @@ pub fn port_validator(port: String) -> Result<(), String> { .map_err(|e| format!("{:?}", e)) } -#[derive(Serialize, Deserialize, Clone, Debug)] -struct ProcessInfo { - rpc_addr: Option, // RPC port to contact the validator at - start_time: u64, // Seconds since the UNIX_EPOCH for when the validator was started -} - -pub fn record_start(ledger_path: &Path, rpc_addr: Option<&SocketAddr>) -> Result<(), io::Error> { - if !ledger_path.exists() { - fs::create_dir_all(&ledger_path)?; - } - - let start_info = ProcessInfo { - rpc_addr: rpc_addr.cloned(), - start_time: SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_secs(), - }; - - let serialized = serde_yaml::to_string(&start_info) - .map_err(|err| io::Error::new(io::ErrorKind::Other, format!("{:?}", err)))?; - - let mut file = File::create(ledger_path.join("process-info.yml"))?; - file.write_all(&serialized.into_bytes())?; - Ok(()) -} - -fn get_validator_process_info( - ledger_path: &Path, -) -> Result<(Option, SystemTime), io::Error> { - let file = File::open(ledger_path.join("process-info.yml"))?; - let config: ProcessInfo = serde_yaml::from_reader(file) - .map_err(|err| io::Error::new(io::ErrorKind::Other, format!("{:?}", err)))?; - - let start_time = SystemTime::UNIX_EPOCH + Duration::from_secs(config.start_time); - Ok((config.rpc_addr, start_time)) -} - -pub fn get_validator_rpc_addr(ledger_path: &Path) -> Result, io::Error> { - get_validator_process_info(ledger_path).map(|process_info| process_info.0) -} - -pub fn get_validator_start_time(ledger_path: &Path) -> Result { - get_validator_process_info(ledger_path).map(|process_info| process_info.1) -} - /// Creates a new process bar for processing that will take an unknown amount of time pub fn new_spinner_progress_bar() -> ProgressBar { let progress_bar = ProgressBar::new(42); diff --git a/validator/src/main.rs b/validator/src/main.rs index 6eaaf3acb8..b7e528989b 100644 --- a/validator/src/main.rs +++ b/validator/src/main.rs @@ -46,8 +46,8 @@ use solana_sdk::{ signature::{Keypair, Signer}, }; use solana_validator::{ - dashboard::Dashboard, get_validator_rpc_addr, new_spinner_progress_bar, println_name_value, - record_start, redirect_stderr_to_file, + admin_rpc_service, dashboard::Dashboard, new_spinner_progress_bar, println_name_value, + redirect_stderr_to_file, }; use std::{ collections::{HashSet, VecDeque}, @@ -67,9 +67,11 @@ use std::{ #[derive(Debug, PartialEq)] enum Operation { + Exit, Initialize, Monitor, Run, + SetLogFilter { filter: String }, WaitForRestartWindow { min_idle_time_in_minutes: usize }, } @@ -82,13 +84,10 @@ fn wait_for_restart_window( let min_idle_slots = (min_idle_time_in_minutes as f64 * 60. / DEFAULT_S_PER_SLOT) as Slot; - let rpc_addr = get_validator_rpc_addr(&ledger_path).map_err(|err| { - format!( - "Unable to read validator RPC address from {}: {}", - ledger_path.display(), - err - ) - })?; + let admin_client = admin_rpc_service::connect(&ledger_path); + let rpc_addr = admin_rpc_service::runtime() + .block_on(async move { admin_client.await?.rpc_addr().await }) + .map_err(|err| format!("Unable to get validator RPC address: {}", err))?; let rpc_client = match rpc_addr { None => return Err("RPC not available".into()), @@ -1085,7 +1084,13 @@ pub fn main() { .value_name("PORT") .takes_value(true) .validator(solana_validator::port_validator) - .help("Use this port for JSON RPC and the next port for the RPC websocket"), + .help("Enable JSON RPC on this port, and the next port for the RPC websocket"), + ) + .arg( + Arg::with_name("minimal_rpc_api") + .long("--minimal-rpc-api") + .takes_value(false) + .help("Only expose the RPC methods required to serve snapshots to other nodes"), ) .arg( Arg::with_name("private_rpc") @@ -1099,20 +1104,6 @@ pub fn main() { .takes_value(false) .help("Do not perform TCP/UDP reachable port checks at start-up") ) - .arg( - Arg::with_name("enable_rpc_exit") - .long("enable-rpc-exit") - .takes_value(false) - .help("Enable the JSON RPC 'validatorExit' API. \ - Only enable in a debug environment"), - ) - .arg( - Arg::with_name("enable_rpc_set_log_filter") - .long("enable-rpc-set-log-filter") - .takes_value(false) - .help("Enable the JSON RPC 'setLogFilter' API. \ - Only enable in a debug environment"), - ) .arg( Arg::with_name("enable_rpc_transaction_history") .long("enable-rpc-transaction-history") @@ -1645,17 +1636,32 @@ pub fn main() { .hidden(true), ) .after_help("The default subcommand is run") + .subcommand( + SubCommand::with_name("exit") + .about("Send an exit request to the validator") + ) .subcommand( SubCommand::with_name("init") .about("Initialize the ledger directory then exit") ) + .subcommand( + SubCommand::with_name("monitor") + .about("Monitor the validator") + ) .subcommand( SubCommand::with_name("run") .about("Run the validator") ) .subcommand( - SubCommand::with_name("monitor") - .about("Monitor the validator") + SubCommand::with_name("set-log-filter") + .about("Adjust the validator log filter") + .arg( + Arg::with_name("filter") + .takes_value(true) + .index(1) + .help("New filter using the same format as the RUST_LOG environment variable") + ) + .after_help("Note: the new filter only applies to the currently running validator instance") ) .subcommand( SubCommand::with_name("wait-for-restart-window") @@ -1675,8 +1681,12 @@ pub fn main() { let operation = match matches.subcommand() { ("", _) | ("run", _) => Operation::Run, + ("exit", _) => Operation::Exit, ("init", _) => Operation::Initialize, ("monitor", _) => Operation::Monitor, + ("set-log-filter", Some(subcommand_matches)) => Operation::SetLogFilter { + filter: value_t_or_exit!(subcommand_matches, "filter", String), + }, ("wait-for-restart-window", Some(subcommand_matches)) => Operation::WaitForRestartWindow { min_idle_time_in_minutes: value_t_or_exit!( subcommand_matches, @@ -1792,8 +1802,6 @@ pub fn main() { expected_shred_version: value_t!(matches, "expected_shred_version", u16).ok(), new_hard_forks: hardforks_of(&matches, "hard_forks"), rpc_config: JsonRpcConfig { - enable_validator_exit: matches.is_present("enable_rpc_exit"), - enable_set_log_filter: matches.is_present("enable_rpc_set_log_filter"), enable_rpc_transaction_history: matches.is_present("enable_rpc_transaction_history"), enable_cpi_and_log_storage: matches.is_present("enable_cpi_and_log_storage"), enable_bigtable_ledger_storage: matches @@ -1803,6 +1811,7 @@ pub fn main() { faucet_addr: matches.value_of("rpc_faucet_addr").map(|address| { solana_net_utils::parse_host_port(address).expect("failed to parse faucet address") }), + minimal_api: matches.is_present("minimal_rpc_api"), max_multiple_accounts: Some(value_t_or_exit!( matches, "rpc_max_multiple_accounts", @@ -2038,8 +2047,28 @@ pub fn main() { }); match operation { + Operation::Exit => { + let admin_client = admin_rpc_service::connect(&ledger_path); + admin_rpc_service::runtime() + .block_on(async move { admin_client.await?.exit().await }) + .unwrap_or_else(|err| { + println!("exit request failed: {}", err); + exit(1); + }); + exit(0); + } + Operation::SetLogFilter { filter } => { + let admin_client = admin_rpc_service::connect(&ledger_path); + admin_rpc_service::runtime() + .block_on(async move { admin_client.await?.set_log_filter(filter).await }) + .unwrap_or_else(|err| { + println!("set log filter failed: {}", err); + exit(1); + }); + exit(0); + } Operation::Monitor => { - let dashboard = Dashboard::new(&ledger_path, None).unwrap_or_else(|err| { + let dashboard = Dashboard::new(&ledger_path, None, None).unwrap_or_else(|err| { println!( "Error: Unable to connect to validator at {}: {:?}", ledger_path.display(), @@ -2070,15 +2099,6 @@ pub fn main() { exit(1); }); - record_start( - &ledger_path, - validator_config - .rpc_addrs - .as_ref() - .map(|(rpc_addr, _)| rpc_addr), - ) - .unwrap_or_else(|err| println!("Error: failed to record validator start: {}", err)); - let logfile = { let logfile = matches .value_of("logfile") @@ -2098,6 +2118,15 @@ pub fn main() { info!("{} {}", crate_name!(), solana_version::version!()); info!("Starting validator with: {:#?}", std::env::args_os()); + admin_rpc_service::run( + &ledger_path, + admin_rpc_service::AdminRpcRequestMetadata { + rpc_addr: validator_config.rpc_addrs.map(|(rpc_addr, _)| rpc_addr), + start_time: std::time::SystemTime::now(), + validator_exit: validator_config.validator_exit.clone(), + }, + ); + let gossip_host: IpAddr = matches .value_of("gossip_host") .map(|gossip_host| {