From fc8ce0d77826b6ec6d66c7337da708788413e78a Mon Sep 17 00:00:00 2001 From: Hansie Odendaal Date: Mon, 4 Dec 2023 18:06:25 +0200 Subject: [PATCH] Add miner input processing Added miner input processing to the minotari miner and merge mining proxy. The miner wallet address, base node gRPC address and basic gRPC connection will be verified. --- Cargo.lock | 21 ++ .../minotari_app_utilities/Cargo.toml | 4 + .../minotari_app_utilities/src/lib.rs | 1 + .../src/parse_miner_input.rs | 195 ++++++++++++++++++ .../src/block_template_protocol.rs | 12 +- .../minotari_merge_mining_proxy/src/error.rs | 7 +- .../minotari_merge_mining_proxy/src/main.rs | 8 +- .../minotari_merge_mining_proxy/src/proxy.rs | 22 +- .../src/run_merge_miner.rs | 105 +++++----- applications/minotari_miner/src/errors.rs | 6 +- applications/minotari_miner/src/run_miner.rs | 106 +++++----- common/config/presets/c_base_node.toml | 54 ++--- 12 files changed, 394 insertions(+), 147 deletions(-) create mode 100644 applications/minotari_app_utilities/src/parse_miner_input.rs diff --git a/Cargo.lock b/Cargo.lock index f5e5766806..f84c8e6fd7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1540,6 +1540,18 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c877555693c14d2f84191cfd3ad8582790fc52b5e2274b40b59cf5f5cea25c7" +[[package]] +name = "dialoguer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59c6f2989294b9a498d3ad5491a79c6deb604617378e1cdc4bfc1c1361fe2f87" +dependencies = [ + "console", + "shell-words", + "tempfile", + "zeroize", +] + [[package]] name = "diesel" version = "2.1.3" @@ -3017,9 +3029,11 @@ name = "minotari_app_utilities" version = "1.0.0-pre.0" dependencies = [ "clap 3.2.25", + "dialoguer", "futures 0.3.29", "json5", "log", + "minotari_app_grpc", "rand", "serde", "tari_common", @@ -3029,6 +3043,7 @@ dependencies = [ "tari_utilities", "thiserror", "tokio", + "tonic 0.6.2", ] [[package]] @@ -5172,6 +5187,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + [[package]] name = "signal-hook" version = "0.3.17" diff --git a/applications/minotari_app_utilities/Cargo.toml b/applications/minotari_app_utilities/Cargo.toml index 62ab8a9de6..f9f7302a75 100644 --- a/applications/minotari_app_utilities/Cargo.toml +++ b/applications/minotari_app_utilities/Cargo.toml @@ -11,6 +11,7 @@ tari_common_types = { path = "../../base_layer/common_types" } tari_comms = { path = "../../comms/core" } tari_features = { path = "../../common/tari_features"} tari_utilities = { version = "0.6" } +minotari_app_grpc = { path = "../minotari_app_grpc" } clap = { version = "3.2", features = ["derive", "env"] } futures = { version = "^0.3.16", default-features = false, features = ["alloc"] } @@ -20,6 +21,9 @@ rand = "0.8" tokio = { version = "1.23", features = ["signal"] } serde = "1.0.126" thiserror = "^1.0.26" +dialoguer = { version = "0.10" } +tonic = "0.6.2" + [build-dependencies] tari_common = { path = "../../common", features = ["build", "static-application-info"] } diff --git a/applications/minotari_app_utilities/src/lib.rs b/applications/minotari_app_utilities/src/lib.rs index 62243bc3b2..ccb7f7ba4c 100644 --- a/applications/minotari_app_utilities/src/lib.rs +++ b/applications/minotari_app_utilities/src/lib.rs @@ -23,6 +23,7 @@ pub mod common_cli_args; pub mod identity_management; pub mod network_check; +pub mod parse_miner_input; pub mod utilities; pub mod consts { diff --git a/applications/minotari_app_utilities/src/parse_miner_input.rs b/applications/minotari_app_utilities/src/parse_miner_input.rs new file mode 100644 index 0000000000..bdc183a496 --- /dev/null +++ b/applications/minotari_app_utilities/src/parse_miner_input.rs @@ -0,0 +1,195 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::{net::SocketAddr, str::FromStr}; + +use dialoguer::Input as InputPrompt; +use minotari_app_grpc::{ + authentication::ClientAuthenticationInterceptor, + tari_rpc::{base_node_client::BaseNodeClient, Block, NewBlockTemplate, NewBlockTemplateRequest}, +}; +use tari_common::configuration::{ + bootstrap::{grpc_default_port, ApplicationType}, + Network, +}; +use tari_common_types::tari_address::TariAddress; +use tari_comms::{multiaddr::Multiaddr, utils::multiaddr::multiaddr_to_socketaddr}; +use thiserror::Error; +use tonic::{codegen::InterceptedService, transport::Channel, Code}; + +/// Error parsing input +#[derive(Debug, Error)] +pub enum ParseInputError { + #[error("Could not convert data:{0}")] + WalletPaymentAddress(String), + #[error("Could not convert data:{0}")] + BaseNodeSocketAddress(String), +} + +/// Read base_node_socket_address arg or prompt for input +pub fn base_node_socket_address( + base_node_grpc_address: Option, + network: Network, +) -> Result { + match base_node_grpc_address { + Some(address) => { + println!("Base node gRPC address: '{}'", address); + match multiaddr_to_socketaddr(&address) { + Ok(val) => Ok(val), + Err(e) => Err(ParseInputError::BaseNodeSocketAddress(format!( + "Error - base node socket address '{}' not valid ({:?})", + address, e + ))), + } + }, + None => { + println!(); + // Get it on the command line + loop { + let mut address = InputPrompt::::new() + .with_prompt("Please enter 'base-node-grpc-address' ('quit' or 'exit' to quit) ") + .default(format!( + "/ip4/127.0.0.1/tcp/{}", + grpc_default_port(ApplicationType::BaseNode, network) + )) + .interact() + .unwrap(); + process_quit(&address); + // Remove leading and trailing whitespace + address = address.trim().to_string(); + let base_node_multi_address: Result = + address.parse().map_err(|e| format!("{:?}", e)); + match base_node_multi_address { + Ok(val) => match multiaddr_to_socketaddr(&val) { + Ok(val) => { + println!(); + return Ok(val); + }, + Err(e) => println!(" Error - base node socket address '{}' not valid ({:?})", val, e), + }, + Err(e) => println!(" Error - base node gRPC address '{}' not valid ({:?})", address, e), + } + } + }, + } +} + +/// Read wallet_payment_address arg or prompt for input +pub fn wallet_payment_address( + config_wallet_payment_address: String, + network: Network, +) -> Result { + // Verify config setting + return match TariAddress::from_str(&config_wallet_payment_address) { + Ok(address) => { + if address == TariAddress::default() { + println!(); + // Get it on the command line + loop { + let mut address = InputPrompt::::new() + .with_prompt("Please enter 'wallet-payment-address' ('quit' or 'exit' to quit) ") + .interact() + .unwrap(); + process_quit(&address); + // Remove leading and trailing whitespace + address = address.trim().to_string(); + let wallet_address: Result = address.parse().map_err(|e| format!("{:?}", e)); + match wallet_address { + Ok(val) => { + if val.network() == network { + return Ok(val); + } else { + println!( + " Error - wallet payment address '{}' does not match miner network '{}'", + address, network + ); + } + }, + Err(e) => println!(" Error - wallet payment address '{}' not valid ({})", address, e), + } + } + } + if address.network() != network { + return Err(ParseInputError::WalletPaymentAddress(format!( + "Wallet payment address '{}' does not match miner network '{}'", + config_wallet_payment_address, network + ))); + } + Ok(address) + }, + Err(err) => Err(ParseInputError::WalletPaymentAddress(format!( + "Wallet payment address '{}' not valid ({})", + config_wallet_payment_address, err + ))), + }; +} + +/// User requested quit +pub fn process_quit(command: &str) { + if command.to_uppercase() == "QUIT" || command.to_uppercase() == "EXIT" { + println!("\nUser requested quit (Press 'Enter')"); + wait_for_keypress(); + std::process::exit(0); + } +} + +/// Wait for a keypress before continuing +pub fn wait_for_keypress() { + use std::io::{stdin, Read}; + let mut stdin = stdin(); + let buf: &mut [u8] = &mut [0; 2]; + let _unused = stdin.read(buf).expect("Error reading keypress"); +} + +/// Base node gRPC client +pub type BaseNodeGrpcClient = BaseNodeClient>; + +/// Verify that the base node is responding to the mining gRPC requests +pub async fn verify_base_node_grpc_mining_responses( + node_conn: &mut BaseNodeGrpcClient, + pow_algo_request: NewBlockTemplateRequest, +) -> Result<(), String> { + let get_new_block_template = node_conn.get_new_block_template(pow_algo_request).await; + if let Err(e) = get_new_block_template { + if e.code() == Code::PermissionDenied { + return Err("'get_new_block_template'".to_string()); + } + }; + let get_tip_info = node_conn.get_tip_info(minotari_app_grpc::tari_rpc::Empty {}).await; + if let Err(e) = get_tip_info { + if e.code() == Code::PermissionDenied { + return Err("'get_tip_info'".to_string()); + } + } + let block_result = node_conn.get_new_block(NewBlockTemplate::default()).await; + if let Err(e) = block_result { + if e.code() == Code::PermissionDenied { + return Err("'get_new_block'".to_string()); + } + } + if let Err(e) = node_conn.submit_block(Block::default()).await { + if e.code() == Code::PermissionDenied { + return Err("'submit_block'".to_string()); + } + } + Ok(()) +} diff --git a/applications/minotari_merge_mining_proxy/src/block_template_protocol.rs b/applications/minotari_merge_mining_proxy/src/block_template_protocol.rs index 9d62a85891..1c44b5bc9e 100644 --- a/applications/minotari_merge_mining_proxy/src/block_template_protocol.rs +++ b/applications/minotari_merge_mining_proxy/src/block_template_protocol.rs @@ -21,10 +21,10 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //! Methods for seting up a new block. -use std::{cmp, convert::TryFrom, str::FromStr, sync::Arc}; +use std::{cmp, convert::TryFrom, sync::Arc}; use log::*; -use minotari_app_grpc::{authentication::ClientAuthenticationInterceptor, tari_rpc::base_node_client::BaseNodeClient}; +use minotari_app_utilities::parse_miner_input::BaseNodeGrpcClient; use minotari_node_grpc_client::grpc; use tari_common_types::{tari_address::TariAddress, types::FixedHash}; use tari_core::{ @@ -36,7 +36,6 @@ use tari_core::{ transaction_components::{TransactionKernel, TransactionOutput}, }, }; -use tonic::{codegen::InterceptedService, transport::Channel}; use crate::{ block_template_data::{BlockTemplateData, BlockTemplateDataBuilder}, @@ -50,7 +49,7 @@ const LOG_TARGET: &str = "minotari_mm_proxy::proxy::block_template_protocol"; /// Structure holding grpc connections. pub struct BlockTemplateProtocol<'a> { config: Arc, - base_node_client: &'a mut BaseNodeClient>, + base_node_client: &'a mut BaseNodeGrpcClient, key_manager: MemoryDbKeyManager, wallet_payment_address: TariAddress, consensus_manager: ConsensusManager, @@ -58,13 +57,12 @@ pub struct BlockTemplateProtocol<'a> { impl<'a> BlockTemplateProtocol<'a> { pub async fn new( - base_node_client: &'a mut BaseNodeClient>, + base_node_client: &'a mut BaseNodeGrpcClient, config: Arc, consensus_manager: ConsensusManager, + wallet_payment_address: TariAddress, ) -> Result, MmProxyError> { let key_manager = create_memory_db_key_manager(); - let wallet_payment_address = TariAddress::from_str(&config.wallet_payment_address) - .map_err(|err| MmProxyError::WalletPaymentAddress(err.to_string()))?; Ok(Self { config, base_node_client, diff --git a/applications/minotari_merge_mining_proxy/src/error.rs b/applications/minotari_merge_mining_proxy/src/error.rs index 8d08675344..27b81a8ca2 100644 --- a/applications/minotari_merge_mining_proxy/src/error.rs +++ b/applications/minotari_merge_mining_proxy/src/error.rs @@ -26,6 +26,7 @@ use std::io; use hex::FromHexError; use hyper::header::InvalidHeaderValue; +use minotari_app_utilities::parse_miner_input::ParseInputError; use minotari_wallet_grpc_client::BasicAuthError; use tari_common::{ConfigError, ConfigurationError}; use tari_core::{ @@ -106,8 +107,10 @@ pub enum MmProxyError { CoreKeyManagerError(#[from] CoreKeyManagerError), #[error("Consensus build error: {0}")] ConsensusBuilderError(#[from] ConsensusBuilderError), - #[error("Could not convert data:{0}")] - WalletPaymentAddress(String), + #[error("Consensus build error: {0}")] + ParseInputError(#[from] ParseInputError), + #[error("Base node not responding to gRPC requests: {0}")] + BaseNodeNotResponding(String), } impl From for MmProxyError { diff --git a/applications/minotari_merge_mining_proxy/src/main.rs b/applications/minotari_merge_mining_proxy/src/main.rs index dad243b6dc..acf42c7349 100644 --- a/applications/minotari_merge_mining_proxy/src/main.rs +++ b/applications/minotari_merge_mining_proxy/src/main.rs @@ -55,5 +55,11 @@ async fn main() -> Result<(), anyhow::Error> { &cli.common.get_base_path(), include_str!("../log4rs_sample.yml"), )?; - run_merge_miner::start_merge_miner(cli).await + match run_merge_miner::start_merge_miner(cli).await { + Ok(_) => Ok(()), + Err(err) => { + eprintln!("Fatal error: {:?}", err); + Err(err) + }, + } } diff --git a/applications/minotari_merge_mining_proxy/src/proxy.rs b/applications/minotari_merge_mining_proxy/src/proxy.rs index 859b773ebf..9476e5bcee 100644 --- a/applications/minotari_merge_mining_proxy/src/proxy.rs +++ b/applications/minotari_merge_mining_proxy/src/proxy.rs @@ -39,16 +39,16 @@ use bytes::Bytes; use hyper::{header::HeaderValue, service::Service, Body, Method, Request, Response, StatusCode, Uri}; use json::json; use jsonrpc::error::StandardError; -use minotari_node_grpc_client::{grpc, grpc::base_node_client::BaseNodeClient}; -use minotari_wallet_grpc_client::ClientAuthenticationInterceptor; +use minotari_app_utilities::parse_miner_input::BaseNodeGrpcClient; +use minotari_node_grpc_client::grpc; use reqwest::{ResponseBuilderExt, Url}; use serde_json as json; +use tari_common_types::tari_address::TariAddress; use tari_core::{ consensus::ConsensusManager, proof_of_work::{monero_rx, monero_rx::FixedByteArray, randomx_difficulty, randomx_factory::RandomXFactory}, }; use tari_utilities::hex::Hex; -use tonic::{codegen::InterceptedService, transport::Channel}; use tracing::{debug, error, info, instrument, trace, warn}; use crate::{ @@ -74,9 +74,10 @@ impl MergeMiningProxyService { pub fn new( config: MergeMiningProxyConfig, http_client: reqwest::Client, - base_node_client: BaseNodeClient>, + base_node_client: BaseNodeGrpcClient, block_templates: BlockTemplateRepository, randomx_factory: RandomXFactory, + wallet_payment_address: TariAddress, ) -> Result { debug!(target: LOG_TARGET, "Config: {:?}", config); let consensus_manager = ConsensusManager::builder(config.network).build()?; @@ -91,6 +92,7 @@ impl MergeMiningProxyService { last_assigned_monerod_server: Arc::new(RwLock::new(None)), randomx_factory, consensus_manager, + wallet_payment_address, }, }) } @@ -156,12 +158,13 @@ struct InnerService { config: Arc, block_templates: BlockTemplateRepository, http_client: reqwest::Client, - base_node_client: BaseNodeClient>, + base_node_client: BaseNodeGrpcClient, initial_sync_achieved: Arc, current_monerod_server: Arc>>, last_assigned_monerod_server: Arc>>, randomx_factory: RandomXFactory, consensus_manager: ConsensusManager, + wallet_payment_address: TariAddress, } impl InnerService { @@ -432,8 +435,13 @@ impl InnerService { } } - let new_block_protocol = - BlockTemplateProtocol::new(&mut grpc_client, self.config.clone(), self.consensus_manager.clone()).await?; + let new_block_protocol = BlockTemplateProtocol::new( + &mut grpc_client, + self.config.clone(), + self.consensus_manager.clone(), + self.wallet_payment_address.clone(), + ) + .await?; let seed_hash = FixedByteArray::from_hex(&monerod_resp["result"]["seed_hash"].to_string().replace('\"', "")) .map_err(|err| MmProxyError::InvalidMonerodResponse(format!("seed hash hex is invalid: {}", err)))?; diff --git a/applications/minotari_merge_mining_proxy/src/run_merge_miner.rs b/applications/minotari_merge_mining_proxy/src/run_merge_miner.rs index 95a4736a55..992e8b3911 100644 --- a/applications/minotari_merge_mining_proxy/src/run_merge_miner.rs +++ b/applications/minotari_merge_mining_proxy/src/run_merge_miner.rs @@ -26,21 +26,19 @@ use futures::future; use hyper::{service::make_service_fn, Server}; use log::*; use minotari_app_grpc::tls::protocol_string; -use minotari_node_grpc_client::grpc::base_node_client::BaseNodeClient; -use minotari_wallet_grpc_client::ClientAuthenticationInterceptor; -use tari_common::{ - configuration::bootstrap::{grpc_default_port, ApplicationType}, - load_configuration, - DefaultConfigLoader, +use minotari_app_utilities::parse_miner_input::{ + base_node_socket_address, + verify_base_node_grpc_mining_responses, + wallet_payment_address, + BaseNodeGrpcClient, }; -use tari_common_types::tari_address::TariAddress; +use minotari_node_grpc_client::{grpc, grpc::base_node_client::BaseNodeClient}; +use minotari_wallet_grpc_client::ClientAuthenticationInterceptor; +use tari_common::{load_configuration, DefaultConfigLoader}; use tari_comms::utils::multiaddr::multiaddr_to_socketaddr; use tari_core::proof_of_work::randomx_factory::RandomXFactory; use tokio::time::Duration; -use tonic::{ - codegen::InterceptedService, - transport::{Certificate, Channel, ClientTlsConfig, Endpoint}, -}; +use tonic::transport::{Certificate, ClientTlsConfig, Endpoint}; use crate::{ block_template_data::BlockTemplateRepository, @@ -49,6 +47,7 @@ use crate::{ proxy::MergeMiningProxyService, Cli, }; + const LOG_TARGET: &str = "minotari_mm_proxy::proxy"; pub async fn start_merge_miner(cli: Cli) -> Result<(), anyhow::Error> { @@ -56,20 +55,6 @@ pub async fn start_merge_miner(cli: Cli) -> Result<(), anyhow::Error> { let cfg = load_configuration(&config_path, true, &cli)?; let mut config = MergeMiningProxyConfig::load_from(&cfg)?; config.set_base_path(cli.common.get_base_path()); - setup_grpc_config(&mut config); - - let wallet_payment_address = TariAddress::from_str(&config.wallet_payment_address) - .map_err(|err| MmProxyError::WalletPaymentAddress("'wallet_payment_address' ".to_owned() + &err.to_string()))?; - if wallet_payment_address == TariAddress::default() { - return Err(anyhow::Error::msg( - "'wallet_payment_address' may not have the default value", - )); - } - if wallet_payment_address.network() != config.network { - return Err(anyhow::Error::msg( - "'wallet_payment_address' network does not match miner network".to_string(), - )); - } info!(target: LOG_TARGET, "Configuration: {:?}", config); let client = reqwest::Client::builder() @@ -79,7 +64,34 @@ pub async fn start_merge_miner(cli: Cli) -> Result<(), anyhow::Error> { .build() .map_err(MmProxyError::ReqwestError)?; - let base_node_client = connect_base_node(&config).await?; + let wallet_payment_address = wallet_payment_address(config.wallet_payment_address.clone(), config.network)?; + let mut base_node_client = match connect_base_node(&config).await { + Ok(client) => client, + Err(e) => { + let msg = format!("Fatal: Could not connect to base node ({})", e); + println!("{}", msg); + error!(target: LOG_TARGET, "{}", msg); + let msg = + "Is the base node's gRPC running? Try running it with `--enable-grpc` or enable it in the config."; + println!("{}", msg); + error!(target: LOG_TARGET, "{}", msg); + return Err(e.into()); + }, + }; + if let Err(e) = verify_base_node_responses(&mut base_node_client).await { + if let MmProxyError::BaseNodeNotResponding(_) = e { + println!(); + println!("{}", e.to_string()); + error!(target: LOG_TARGET, "{}", e.to_string()); + let msg = "Are the base node's gRPC mining methods denied in its 'config.toml'? Please ensure these \ + methods are commented out:\n 'grpc_server_deny_methods': \"get_new_block_template\", \ + \"get_tip_info\", \"get_new_block\", \"submit_block\""; + println!("{}", msg); + error!(target: LOG_TARGET, "{}", msg); + println!(); + return Err(e.into()); + } + } let listen_addr = multiaddr_to_socketaddr(&config.listener_address)?; let randomx_factory = RandomXFactory::new(config.max_randomx_vms); @@ -89,6 +101,7 @@ pub async fn start_merge_miner(cli: Cli) -> Result<(), anyhow::Error> { base_node_client, BlockTemplateRepository::new(), randomx_factory, + wallet_payment_address, )?; let service = make_service_fn(|_conn| future::ready(Result::<_, Infallible>::Ok(randomx_service.clone()))); @@ -112,20 +125,29 @@ pub async fn start_merge_miner(cli: Cli) -> Result<(), anyhow::Error> { } } -async fn connect_base_node( - config: &MergeMiningProxyConfig, -) -> Result>, MmProxyError> { +async fn verify_base_node_responses(node_conn: &mut BaseNodeGrpcClient) -> Result<(), MmProxyError> { + if let Err(e) = verify_base_node_grpc_mining_responses(node_conn, grpc::NewBlockTemplateRequest { + algo: Some(grpc::PowAlgo { + pow_algo: grpc::pow_algo::PowAlgos::Randomx.into(), + }), + max_weight: 0, + }) + .await + { + return Err(MmProxyError::BaseNodeNotResponding(e)); + } + Ok(()) +} + +async fn connect_base_node(config: &MergeMiningProxyConfig) -> Result { + let socketaddr = base_node_socket_address(config.base_node_grpc_address.clone(), config.network)?; let base_node_addr = format!( "{}{}", protocol_string(config.base_node_grpc_tls_domain_name.is_some()), - multiaddr_to_socketaddr( - &config - .base_node_grpc_address - .clone() - .expect("Base node grpc address not found") - )?, + socketaddr, ); + println!("👛 Connecting to base node at {}", base_node_addr); info!(target: LOG_TARGET, "👛 Connecting to base node at {}", base_node_addr); let mut endpoint = Endpoint::from_str(&base_node_addr)?; @@ -152,16 +174,3 @@ async fn connect_base_node( Ok(node_conn) } - -fn setup_grpc_config(config: &mut MergeMiningProxyConfig) { - if config.base_node_grpc_address.is_none() { - config.base_node_grpc_address = Some( - format!( - "/ip4/127.0.0.1/tcp/{}", - grpc_default_port(ApplicationType::BaseNode, config.network) - ) - .parse() - .unwrap(), - ); - } -} diff --git a/applications/minotari_miner/src/errors.rs b/applications/minotari_miner/src/errors.rs index ecee01405e..2f5226c47a 100644 --- a/applications/minotari_miner/src/errors.rs +++ b/applications/minotari_miner/src/errors.rs @@ -21,7 +21,7 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // use minotari_app_grpc::authentication::BasicAuthError; -use tari_core::consensus::ConsensusBuilderError; +use minotari_app_utilities::parse_miner_input::ParseInputError; use thiserror::Error; use tonic::codegen::http::uri::InvalidUri; @@ -54,7 +54,9 @@ pub enum MinerError { #[error("Coinbase error: {0}")] CoinbaseError(String), #[error("Consensus build error: {0}")] - ConsensusBuilderError(#[from] ConsensusBuilderError), + ParseInputError(#[from] ParseInputError), + #[error("Base node not responding to gRPC requests: {0}")] + BaseNodeNotResponding(String), } pub fn err_empty(name: &str) -> MinerError { diff --git a/applications/minotari_miner/src/run_miner.rs b/applications/minotari_miner/src/run_miner.rs index f8a2dcd8ea..7ce8abc7b4 100644 --- a/applications/minotari_miner/src/run_miner.rs +++ b/applications/minotari_miner/src/run_miner.rs @@ -29,15 +29,21 @@ use minotari_app_grpc::{ tari_rpc::{base_node_client::BaseNodeClient, TransactionOutput as GrpcTransactionOutput}, tls::protocol_string, }; -use minotari_app_utilities::network_check::set_network_if_choice_valid; +use minotari_app_utilities::{ + network_check::set_network_if_choice_valid, + parse_miner_input::{ + base_node_socket_address, + verify_base_node_grpc_mining_responses, + wallet_payment_address, + BaseNodeGrpcClient, + }, +}; use tari_common::{ - configuration::bootstrap::{grpc_default_port, ApplicationType}, exit_codes::{ExitCode, ExitError}, load_configuration, DefaultConfigLoader, }; use tari_common_types::tari_address::TariAddress; -use tari_comms::utils::multiaddr::multiaddr_to_socketaddr; use tari_core::{ blocks::BlockHeader, consensus::ConsensusManager, @@ -50,10 +56,7 @@ use tari_core::{ use tari_crypto::ristretto::RistrettoPublicKey; use tari_utilities::hex::Hex; use tokio::time::sleep; -use tonic::{ - codegen::InterceptedService, - transport::{Certificate, Channel, ClientTlsConfig, Endpoint}, -}; +use tonic::transport::{Certificate, ClientTlsConfig, Endpoint}; use crate::{ cli::Cli, @@ -66,8 +69,6 @@ use crate::{ pub const LOG_TARGET: &str = "minotari::miner::main"; pub const LOG_TARGET_FILE: &str = "minotari::logging::miner::main"; -type BaseNodeGrpcClient = BaseNodeClient>; - #[allow(clippy::too_many_lines)] pub async fn start_miner(cli: Cli) -> Result<(), ExitError> { let config_path = cli.common.config_path(); @@ -78,27 +79,16 @@ pub async fn start_miner(cli: Cli) -> Result<(), ExitError> { set_network_if_choice_valid(config.network)?; debug!(target: LOG_TARGET_FILE, "{:?}", config); - setup_grpc_config(&mut config); + // setup_grpc_config(&mut config); let key_manager = create_memory_db_key_manager(); - let wallet_payment_address = TariAddress::from_str(&config.wallet_payment_address).map_err(|err| { - ExitError::new( - ExitCode::WalletPaymentAddress, - "'wallet_payment_address' ".to_owned() + &err.to_string(), - ) - })?; + let wallet_payment_address = wallet_payment_address(config.wallet_payment_address.clone(), config.network) + .map_err(|err| { + ExitError::new( + ExitCode::WalletPaymentAddress, + "'wallet_payment_address' ".to_owned() + &err.to_string(), + ) + })?; debug!(target: LOG_TARGET_FILE, "wallet_payment_address: {}", wallet_payment_address); - if wallet_payment_address == TariAddress::default() { - return Err(ExitError::new( - ExitCode::WalletPaymentAddress, - "'wallet_payment_address' may not have the default value".to_string(), - )); - } - if wallet_payment_address.network() != config.network { - return Err(ExitError::new( - ExitCode::WalletPaymentAddress, - "'wallet_payment_address' network does not match miner network".to_string(), - )); - } let consensus_manager = ConsensusManager::builder(config.network) .build() .map_err(|err| ExitError::new(ExitCode::ConsensusManagerBuilderError, err.to_string()))?; @@ -144,9 +134,23 @@ pub async fn start_miner(cli: Cli) -> Result<(), ExitError> { let mut node_conn = connect(&config).await.map_err(|e| { ExitError::new( ExitCode::GrpcError, - format!("Could not connect to wallet or base node: {}", e), + format!("Could not connect to base node: {}", e.to_string()), ) })?; + if let Err(e) = verify_base_node_responses(&mut node_conn, &config).await { + if let MinerError::BaseNodeNotResponding(_) = e { + println!(); + println!("{}", e.to_string()); + error!(target: LOG_TARGET, "{}", e.to_string()); + let msg = "Are the base node's gRPC mining methods denied in its 'config.toml'? Please ensure these \ + methods are commented out:\n 'grpc_server_deny_methods': \"get_new_block_template\", \ + \"get_tip_info\", \"get_new_block\", \"submit_block\""; + println!("{}", msg); + error!(target: LOG_TARGET, "{}", msg); + println!(); + return Err(ExitError::new(ExitCode::GrpcError, e.to_string())); + } + } let mut blocks_found: u64 = 0; loop { @@ -216,11 +220,13 @@ async fn connect(config: &MinerConfig) -> Result let node_conn = match connect_base_node(config).await { Ok(client) => client, Err(e) => { - error!(target: LOG_TARGET, "Could not connect to base node"); - error!( - target: LOG_TARGET, - "Is its grpc running? try running it with `--enable-grpc` or enable it in config" - ); + let msg = format!("Fatal: Could not connect to base node ({})", e); + println!("{}", msg); + error!(target: LOG_TARGET, "{}", msg); + let msg = + "Is the base node's gRPC running? Try running it with `--enable-grpc` or enable it in the config."; + println!("{}", msg); + error!(target: LOG_TARGET, "{}", msg); return Err(e); }, }; @@ -229,17 +235,14 @@ async fn connect(config: &MinerConfig) -> Result } async fn connect_base_node(config: &MinerConfig) -> Result { + let socketaddr = base_node_socket_address(config.base_node_grpc_address.clone(), config.network)?; let base_node_addr = format!( "{}{}", protocol_string(config.base_node_grpc_tls_domain_name.is_some()), - multiaddr_to_socketaddr( - &config - .base_node_grpc_address - .clone() - .expect("Base node grpc address not found") - )?, + socketaddr, ); + println!("👛 Connecting to base node at {}", base_node_addr); info!(target: LOG_TARGET, "👛 Connecting to base node at {}", base_node_addr); let mut endpoint = Endpoint::from_str(&base_node_addr)?; @@ -267,6 +270,16 @@ async fn connect_base_node(config: &MinerConfig) -> Result Result<(), MinerError> { + if let Err(e) = verify_base_node_grpc_mining_responses(node_conn, config.pow_algo_request()).await { + return Err(MinerError::BaseNodeNotResponding(e)); + } + Ok(()) +} + #[allow(clippy::too_many_lines)] async fn mining_cycle( node_conn: &mut BaseNodeGrpcClient, @@ -430,16 +443,3 @@ async fn validate_tip( } Ok(()) } - -fn setup_grpc_config(config: &mut MinerConfig) { - if config.base_node_grpc_address.is_none() { - config.base_node_grpc_address = Some( - format!( - "/ip4/127.0.0.1/tcp/{}", - grpc_default_port(ApplicationType::BaseNode, config.network) - ) - .parse() - .unwrap(), - ); - } -} diff --git a/common/config/presets/c_base_node.toml b/common/config/presets/c_base_node.toml index aefb01b5c8..827c497f32 100644 --- a/common/config/presets/c_base_node.toml +++ b/common/config/presets/c_base_node.toml @@ -51,33 +51,33 @@ grpc_server_deny_methods = [ "get_tip_info", "identify", "get_network_status", - #"list_headers" - #"get_header_by_hash" - #"get_blocks" - #"get_block_timing" - #"get_constants" - #"get_block_size" - #"get_block_fees" - #"get_tokens_in_circulation" - #"get_network_difficulty" - #"get_new_block_template" - #"get_new_block" - #"get_new_block_blob" - #"submit_block" - #"submit_block_blob" - #"submit_transaction" - #"search_kernels" - #"search_utxos" - #"fetch_matching_utxos" - #"get_peers" - #"get_mempool_transactions" - #"transaction_state" - #"list_connected_peers" - #"get_mempool_stats" - #"get_active_validator_nodes" - #"get_shard_key" - #"get_template_registrations" - #"get_side_chain_utxos" + #"list_headers", + #"get_header_by_hash", + #"get_blocks", + #"get_block_timing", + #"get_constants", + #"get_block_size", + #"get_block_fees", + #"get_tokens_in_circulation", + #"get_network_difficulty", + #"get_new_block_template", + #"get_new_block", + #"get_new_block_blob", + #"submit_block", + #"submit_block_blob", + #"submit_transaction", + #"search_kernels", + #"search_utxos", + #"fetch_matching_utxos", + #"get_peers", + #"get_mempool_transactions", + #"transaction_state", + #"list_connected_peers", + #"get_mempool_stats", + #"get_active_validator_nodes", + #"get_shard_key", + #"get_template_registrations", + #"get_side_chain_utxos", ] # A path to the file that stores your node identity and secret key (default = "config/base_node_id.json")