// Copyright 2021 The Grin Developers // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /// Grin client commands processing use std::net::SocketAddr; use clap::ArgMatches; use crate::api::client; use crate::api::json_rpc::*; use crate::api::types::Status; use crate::config::GlobalConfig; use crate::p2p::types::PeerInfoDisplay; use crate::util::file::get_first_line; use serde_json::json; const ENDPOINT: &str = "/v2/owner"; #[derive(Clone)] pub struct HTTPNodeClient { node_url: String, node_api_secret: Option, } impl HTTPNodeClient { /// Create a new client that will communicate with the given grin node pub fn new(node_url: &str, node_api_secret: Option) -> HTTPNodeClient { HTTPNodeClient { node_url: node_url.to_owned(), node_api_secret: node_api_secret, } } fn send_json_request( &self, method: &str, params: &serde_json::Value, ) -> Result { let timeout = match method { // 6 hours read timeout "validate_chain" => client::TimeOut::new(20, 21600, 20), _ => client::TimeOut::default(), }; let url = format!("http://{}{}", self.node_url, ENDPOINT); let req = build_request(method, params); let res = client::post::( url.as_str(), self.node_api_secret.clone(), &req, timeout, ); match res { Err(e) => { let report = format!("Error calling {}: {}", method, e); error!("{}", report); Err(Error::RPCError) } Ok(inner) => match inner.clone().into_result() { Ok(r) => Ok(r), Err(e) => { error!("{:?}", inner); let report = format!("Unable to parse response for {}: {}", method, e); error!("{}", report); Err(Error::RPCError) } }, } } pub fn show_status(&self) { println!(); let title = "Grin Server Status".to_string(); if term::stdout().is_none() { println!("Could not open terminal"); return; } let mut t = term::stdout().unwrap(); let mut e = term::stdout().unwrap(); t.fg(term::color::MAGENTA).unwrap(); writeln!(t, "{}", title).unwrap(); writeln!(t, "--------------------------").unwrap(); t.reset().unwrap(); match self.send_json_request::("get_status", &serde_json::Value::Null) { Ok(status) => { writeln!(e, "Chain type: {}", status.chain).unwrap(); writeln!(e, "Protocol version: {:?}", status.protocol_version).unwrap(); writeln!(e, "User agent: {}", status.user_agent).unwrap(); writeln!(e, "Connections: {}", status.connections).unwrap(); writeln!(e, "Chain height: {}", status.tip.height).unwrap(); writeln!(e, "Last block hash: {}", status.tip.last_block_pushed).unwrap(); writeln!(e, "Previous block hash: {}", status.tip.prev_block_to_last).unwrap(); writeln!(e, "Total difficulty: {}", status.tip.total_difficulty).unwrap(); writeln!(e, "Sync status: {}", status.sync_status).unwrap(); if let Some(sync_info) = status.sync_info { writeln!(e, "Sync info: {}", sync_info).unwrap(); } } Err(_) => writeln!( e, "WARNING: Client failed to get data. Is your `grin server` offline or broken?" ) .unwrap(), }; e.reset().unwrap(); println!() } pub fn list_connected_peers(&self) { let mut e = term::stdout().unwrap(); match self.send_json_request::>( "get_connected_peers", &serde_json::Value::Null, ) { Ok(connected_peers) => { for (index, connected_peer) in connected_peers.into_iter().enumerate() { writeln!(e, "Peer {}:", index).unwrap(); writeln!(e, "Capabilities: {:?}", connected_peer.capabilities).unwrap(); writeln!(e, "User agent: {}", connected_peer.user_agent).unwrap(); writeln!(e, "Version: {:?}", connected_peer.version).unwrap(); writeln!(e, "Peer address: {}", connected_peer.addr).unwrap(); writeln!(e, "Height: {}", connected_peer.height).unwrap(); writeln!(e, "Total difficulty: {}", connected_peer.total_difficulty).unwrap(); writeln!(e, "Direction: {:?}", connected_peer.direction).unwrap(); println!(); } } Err(_) => writeln!(e, "Failed to get connected peers").unwrap(), }; e.reset().unwrap(); } pub fn reset_chain_head(&self, hash: String) { let mut e = term::stdout().unwrap(); let params = json!([hash]); match self.send_json_request::<()>("reset_chain_head", ¶ms) { Ok(_) => writeln!(e, "Successfully reset chain head {}", hash).unwrap(), Err(_) => writeln!(e, "Failed to reset chain head {}", hash).unwrap(), } e.reset().unwrap(); } pub fn invalidate_header(&self, hash: String) { let mut e = term::stdout().unwrap(); let params = json!([hash]); match self.send_json_request::<()>("invalidate_header", ¶ms) { Ok(_) => writeln!(e, "Successfully invalidated header: {}", hash).unwrap(), Err(_) => writeln!(e, "Failed to invalidate header: {}", hash).unwrap(), } e.reset().unwrap(); } pub fn verify_chain(&self, assume_valid_rangeproofs_kernels: bool) { let mut e = term::stdout().unwrap(); let params = json!([assume_valid_rangeproofs_kernels]); writeln!( e, "Checking the state of the chain. This might take time..." ) .unwrap(); match self.send_json_request::<()>("validate_chain", ¶ms) { Ok(_) => { if assume_valid_rangeproofs_kernels { writeln!(e, "Successfully validated the sum of kernel excesses! [fast_verification enabled]").unwrap() } else { writeln!(e, "Successfully validated the sum of kernel excesses, kernel signature and rangeproofs!").unwrap() } } Err(err) => writeln!(e, "Failed to validate chain: {:?}", err).unwrap(), } e.reset().unwrap(); } pub fn ban_peer(&self, peer_addr: &SocketAddr) { let mut e = term::stdout().unwrap(); let params = json!([peer_addr]); match self.send_json_request::<()>("ban_peer", ¶ms) { Ok(_) => writeln!(e, "Successfully banned peer {}", peer_addr).unwrap(), Err(_) => writeln!(e, "Failed to ban peer {}", peer_addr).unwrap(), }; e.reset().unwrap(); } pub fn unban_peer(&self, peer_addr: &SocketAddr) { let mut e = term::stdout().unwrap(); let params = json!([peer_addr]); match self.send_json_request::<()>("unban_peer", ¶ms) { Ok(_) => writeln!(e, "Successfully unbanned peer {}", peer_addr).unwrap(), Err(_) => writeln!(e, "Failed to unban peer {}", peer_addr).unwrap(), }; e.reset().unwrap(); } } pub fn client_command(client_args: &ArgMatches<'_>, global_config: GlobalConfig) -> i32 { // just get defaults from the global config let server_config = global_config.members.unwrap().server; let api_secret = get_first_line(server_config.api_secret_path.clone()); let node_client = HTTPNodeClient::new(&server_config.api_http_addr, api_secret); match client_args.subcommand() { ("status", Some(_)) => { node_client.show_status(); } ("listconnectedpeers", Some(_)) => { node_client.list_connected_peers(); } ("resetchainhead", Some(args)) => { let hash = args.value_of("hash").unwrap(); node_client.reset_chain_head(hash.to_string()); } ("invalidateheader", Some(args)) => { let hash = args.value_of("hash").unwrap(); node_client.invalidate_header(hash.to_string()); } ("verify-chain", Some(args)) => { let assume_valid_rangeproofs_kernels = args.is_present("fast"); node_client.verify_chain(assume_valid_rangeproofs_kernels); } ("ban", Some(peer_args)) => { let peer = peer_args.value_of("peer").unwrap(); if let Ok(addr) = peer.parse() { node_client.ban_peer(&addr); } else { panic!("Invalid peer address format"); } } ("unban", Some(peer_args)) => { let peer = peer_args.value_of("peer").unwrap(); if let Ok(addr) = peer.parse() { node_client.unban_peer(&addr); } else { panic!("Invalid peer address format"); } } _ => panic!("Unknown client command, use 'grin help client' for details"), } 0 } /// Error type wrapping underlying module errors. #[derive(Debug)] enum Error { /// RPC Error RPCError, }