diff --git a/applications/tari_base_node/src/grpc/base_node_grpc_server.rs b/applications/tari_base_node/src/grpc/base_node_grpc_server.rs index 8871699740..86ffce82bc 100644 --- a/applications/tari_base_node/src/grpc/base_node_grpc_server.rs +++ b/applications/tari_base_node/src/grpc/base_node_grpc_server.rs @@ -36,15 +36,16 @@ use tari_app_grpc::{ tari_rpc::{CalcType, Sorting}, }; use tari_app_utilities::consts; -use tari_comms::CommsNode; +use tari_comms::{Bytes, CommsNode}; use tari_core::{ base_node::{ - comms_interface::Broadcast, + comms_interface::{Broadcast, CommsInterfaceError}, state_machine_service::states::BlockSyncInfo, LocalNodeCommsInterface, StateMachineHandle, }, blocks::{Block, BlockHeader, NewBlockTemplate}, + chain_storage::ChainStorageError, consensus::{emission::Emission, ConsensusManager, NetworkConsensus}, crypto::tari_utilities::{hex::Hex, ByteArray}, mempool::{service::LocalMempoolService, TxStorageResponse}, @@ -443,10 +444,18 @@ impl tari_rpc::base_node_server::BaseNode for BaseNodeGrpcServer { let mut handler = self.node_service.clone(); - let new_block = handler - .get_new_block(block_template) - .await - .map_err(|e| Status::internal(e.to_string()))?; + let new_block = match handler.get_new_block(block_template).await { + Ok(b) => b, + Err(CommsInterfaceError::ChainStorageError(ChainStorageError::CannotCalculateNonTipMmr(msg))) => { + let status = Status::with_details( + tonic::Code::FailedPrecondition, + msg, + Bytes::from_static(b"CannotCalculateNonTipMmr"), + ); + return Err(status); + }, + Err(e) => return Err(Status::internal(e.to_string())), + }; // construct response let block_hash = new_block.hash(); let mining_hash = new_block.header.merged_mining_hash(); diff --git a/applications/tari_merge_mining_proxy/src/block_template_data.rs b/applications/tari_merge_mining_proxy/src/block_template_data.rs index 497de5ef86..64255c1afb 100644 --- a/applications/tari_merge_mining_proxy/src/block_template_data.rs +++ b/applications/tari_merge_mining_proxy/src/block_template_data.rs @@ -22,8 +22,8 @@ use crate::error::MmProxyError; use chrono::{self, DateTime, Duration, Utc}; use std::{collections::HashMap, sync::Arc}; -use tari_app_grpc::tari_rpc::{Block, MinerData}; -use tari_core::{crypto::tari_utilities::hex::Hex, proof_of_work::monero_rx::FixedByteArray}; +use tari_app_grpc::tari_rpc as grpc; +use tari_core::proof_of_work::monero_rx::FixedByteArray; use tokio::sync::RwLock; use tracing::trace; @@ -102,8 +102,8 @@ impl BlockTemplateRepository { #[derive(Clone, Debug)] pub struct BlockTemplateData { pub monero_seed: FixedByteArray, - pub tari_block: Block, - pub tari_miner_data: MinerData, + pub tari_block: grpc::Block, + pub tari_miner_data: grpc::MinerData, pub monero_difficulty: u64, pub tari_difficulty: u64, } @@ -112,25 +112,29 @@ impl BlockTemplateData {} #[derive(Default)] pub struct BlockTemplateDataBuilder { - monero_seed: Option, - tari_block: Option, - tari_miner_data: Option, + monero_seed: Option, + tari_block: Option, + tari_miner_data: Option, monero_difficulty: Option, tari_difficulty: Option, } impl BlockTemplateDataBuilder { - pub fn monero_seed(mut self, monero_seed: String) -> Self { + pub fn new() -> Self { + Default::default() + } + + pub fn monero_seed(mut self, monero_seed: FixedByteArray) -> Self { self.monero_seed = Some(monero_seed); self } - pub fn tari_block(mut self, tari_block: Block) -> Self { + pub fn tari_block(mut self, tari_block: grpc::Block) -> Self { self.tari_block = Some(tari_block); self } - pub fn tari_miner_data(mut self, miner_data: MinerData) -> Self { + pub fn tari_miner_data(mut self, miner_data: grpc::MinerData) -> Self { self.tari_miner_data = Some(miner_data); self } @@ -163,7 +167,7 @@ impl BlockTemplateDataBuilder { .ok_or_else(|| MmProxyError::MissingDataError("tari_difficulty not provided".to_string()))?; Ok(BlockTemplateData { - monero_seed: FixedByteArray::from_hex(&monero_seed).map_err(|_| MmProxyError::InvalidRandomXSeed)?, + monero_seed, tari_block, tari_miner_data, monero_difficulty, diff --git a/applications/tari_merge_mining_proxy/src/block_template_protocol.rs b/applications/tari_merge_mining_proxy/src/block_template_protocol.rs new file mode 100644 index 0000000000..8970249fd2 --- /dev/null +++ b/applications/tari_merge_mining_proxy/src/block_template_protocol.rs @@ -0,0 +1,272 @@ +// 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 crate::{ + block_template_data::{BlockTemplateData, BlockTemplateDataBuilder}, + common::merge_mining, + error::MmProxyError, +}; +use log::*; +use std::cmp; +use tari_app_grpc::tari_rpc as grpc; +use tari_core::proof_of_work::{monero_rx, monero_rx::FixedByteArray, Difficulty}; + +const LOG_TARGET: &str = "tari_mm_proxy::proxy::block_template_protocol"; + +pub struct BlockTemplateProtocol<'a> { + base_node_client: &'a mut grpc::base_node_client::BaseNodeClient, + wallet_client: &'a mut grpc::wallet_client::WalletClient, +} + +impl<'a> BlockTemplateProtocol<'a> { + pub fn new( + base_node_client: &'a mut grpc::base_node_client::BaseNodeClient, + wallet_client: &'a mut grpc::wallet_client::WalletClient, + ) -> Self { + Self { + base_node_client, + wallet_client, + } + } +} + +impl BlockTemplateProtocol<'_> { + pub async fn get_next_block_template( + mut self, + monero_mining_data: MoneroMiningData, + ) -> Result { + loop { + let new_template = self.get_new_block_template().await?; + let coinbase = self.get_coinbase(&new_template).await?; + + let template_height = new_template.template.header.as_ref().map(|h| h.height).unwrap_or(0); + if !self.check_expected_tip(template_height).await? { + debug!( + target: LOG_TARGET, + "Chain tip has progressed past template height {}. Fetching a new block template.", template_height + ); + continue; + } + + debug!(target: LOG_TARGET, "Added coinbase to new block template"); + let block_template_with_coinbase = merge_mining::add_coinbase(coinbase, new_template.template.clone())?; + info!( + target: LOG_TARGET, + "Received new block template from Tari base node for height #{}", + new_template + .template + .header + .as_ref() + .map(|h| h.height) + .unwrap_or_default(), + ); + let block = match self.get_new_block(block_template_with_coinbase).await { + Ok(b) => b, + Err(MmProxyError::FailedPreconditionBlockLostRetry) => { + debug!( + target: LOG_TARGET, + "Chain tip has progressed past template height {}. Fetching a new block template.", + template_height + ); + continue; + }, + Err(err) => return Err(err), + }; + + let final_block = self.add_monero_data(block, monero_mining_data, new_template)?; + return Ok(final_block); + } + } + + async fn get_new_block( + &mut self, + template: grpc::NewBlockTemplate, + ) -> Result { + let resp = self.base_node_client.get_new_block(template).await; + + match resp { + Ok(resp) => Ok(resp.into_inner()), + Err(status) => { + if status.code() == tonic::Code::FailedPrecondition { + return Err(MmProxyError::FailedPreconditionBlockLostRetry); + } + Err(status.into()) + }, + } + } + + async fn get_new_block_template(&mut self) -> Result { + let grpc::NewBlockTemplateResponse { + miner_data, + new_block_template: template, + initial_sync_achieved, + } = self + .base_node_client + .get_new_block_template(grpc::NewBlockTemplateRequest { + algo: Some(grpc::PowAlgo { + pow_algo: grpc::pow_algo::PowAlgos::Monero.into(), + }), + max_weight: 0, + }) + .await + .map_err(|status| MmProxyError::GrpcRequestError { + status, + details: "failed to get new block template".to_string(), + })? + .into_inner(); + + let miner_data = miner_data.ok_or(MmProxyError::GrpcResponseMissingField("miner_data"))?; + let template = template.ok_or(MmProxyError::GrpcResponseMissingField("new_block_template"))?; + Ok(NewBlockTemplateData { + template, + miner_data, + initial_sync_achieved, + }) + } + + async fn check_expected_tip(&mut self, height: u64) -> Result { + let tip = self + .base_node_client + .clone() + .get_tip_info(tari_app_grpc::tari_rpc::Empty {}) + .await? + .into_inner(); + let tip_height = tip.metadata.as_ref().map(|m| m.height_of_longest_chain).unwrap_or(0); + + if height <= tip_height { + warn!( + target: LOG_TARGET, + "Base node received next block (height={}) that has invalidated the block template (height={})", + tip_height, + height + ); + return Ok(false); + } + Ok(true) + } + + async fn get_coinbase(&mut self, template: &NewBlockTemplateData) -> Result { + let miner_data = &template.miner_data; + let tari_height = template.height(); + let block_reward = miner_data.reward; + let total_fees = miner_data.total_fees; + + let coinbase_response = self + .wallet_client + .get_coinbase(grpc::GetCoinbaseRequest { + reward: block_reward, + fee: total_fees, + height: tari_height, + }) + .await + .map_err(|status| MmProxyError::GrpcRequestError { + status, + details: "failed to get new block template".to_string(), + })?; + let coinbase = coinbase_response + .into_inner() + .transaction + .ok_or_else(|| MmProxyError::MissingDataError("Coinbase Invalid".to_string()))?; + Ok(coinbase) + } + + fn add_monero_data( + &self, + tari_block: grpc::GetNewBlockResult, + monero_mining_data: MoneroMiningData, + template_data: NewBlockTemplateData, + ) -> Result { + debug!(target: LOG_TARGET, "New block received from Tari: {:?}", tari_block); + + let tari_difficulty = template_data.miner_data.target_difficulty; + let block_template_data = BlockTemplateDataBuilder::new() + .tari_block( + tari_block + .block + .ok_or(MmProxyError::GrpcResponseMissingField("block"))?, + ) + .tari_miner_data(template_data.miner_data) + .monero_seed(monero_mining_data.seed_hash) + .monero_difficulty(monero_mining_data.difficulty) + .tari_difficulty(tari_difficulty) + .build()?; + + // Deserialize the block template blob + debug!(target: LOG_TARGET, "Deserializing Blocktemplate Blob into Monero Block",); + let mut monero_block = monero_rx::deserialize_monero_block_from_hex(&monero_mining_data.blocktemplate_blob)?; + + debug!(target: LOG_TARGET, "Appending Merged Mining Tag",); + // Add the Tari merge mining tag to the retrieved block template + monero_rx::append_merge_mining_tag(&mut monero_block, &tari_block.merge_mining_hash)?; + + debug!(target: LOG_TARGET, "Creating blockhashing blob from blocktemplate blob",); + // Must be done after the tag is inserted since it will affect the hash of the miner tx + let blockhashing_blob = monero_rx::create_blockhashing_blob_from_block(&monero_block)?; + let blocktemplate_blob = monero_rx::serialize_monero_block_to_hex(&monero_block)?; + + let monero_difficulty = monero_mining_data.difficulty; + let mining_difficulty = cmp::min(monero_difficulty, tari_difficulty); + info!( + target: LOG_TARGET, + "Difficulties: Tari ({}), Monero({}), Selected({})", + tari_difficulty, + monero_mining_data.difficulty, + mining_difficulty + ); + Ok(FinalBlockTemplateData { + template: block_template_data, + target_difficulty: mining_difficulty.into(), + blockhashing_blob, + blocktemplate_blob, + merge_mining_hash: tari_block.merge_mining_hash, + }) + } +} + +/// Private convenience container struct for new template data +struct NewBlockTemplateData { + pub template: grpc::NewBlockTemplate, + pub miner_data: grpc::MinerData, + pub initial_sync_achieved: bool, +} + +impl NewBlockTemplateData { + pub fn height(&self) -> u64 { + self.template.header.as_ref().map(|h| h.height).unwrap_or(0) + } +} + +/// Final outputs for required for merge mining +pub struct FinalBlockTemplateData { + pub template: BlockTemplateData, + pub target_difficulty: Difficulty, + pub blockhashing_blob: String, + pub blocktemplate_blob: String, + pub merge_mining_hash: Vec, +} + +/// Container struct for monero mining data inputs obtained from monerod +pub struct MoneroMiningData { + pub seed_hash: FixedByteArray, + pub blocktemplate_blob: String, + pub difficulty: u64, +} diff --git a/applications/tari_merge_mining_proxy/src/common/merge_mining.rs b/applications/tari_merge_mining_proxy/src/common/merge_mining.rs index b4be5ee223..7d232069b7 100644 --- a/applications/tari_merge_mining_proxy/src/common/merge_mining.rs +++ b/applications/tari_merge_mining_proxy/src/common/merge_mining.rs @@ -29,22 +29,16 @@ use tari_core::{ }; pub fn add_coinbase( - coinbase: Option, - mut block: NewBlockTemplate, + coinbase: grpc::Transaction, + block_template: grpc::NewBlockTemplate, ) -> Result { - if let Some(tx) = coinbase { - let output = TransactionOutput::try_from(tx.clone().body.unwrap().outputs[0].clone()) - .map_err(MmProxyError::MissingDataError)?; - let kernel = - TransactionKernel::try_from(tx.body.unwrap().kernels[0].clone()).map_err(MmProxyError::MissingDataError)?; - block.body.add_output(output); - block.body.add_kernel(kernel); - let template = grpc::NewBlockTemplate::try_from(block); - match template { - Ok(template) => Ok(template), - Err(_e) => Err(MmProxyError::MissingDataError("Template Invalid".to_string())), - } - } else { - Err(MmProxyError::MissingDataError("Coinbase Invalid".to_string())) - } + let mut block_template = NewBlockTemplate::try_from(block_template) + .map_err(|e| MmProxyError::MissingDataError(format!("GRPC Conversion Error: {}", e)))?; + let output = TransactionOutput::try_from(coinbase.body.as_ref().unwrap().outputs[0].clone()) + .map_err(MmProxyError::MissingDataError)?; + let kernel = TransactionKernel::try_from(coinbase.body.as_ref().unwrap().kernels[0].clone()) + .map_err(MmProxyError::MissingDataError)?; + block_template.body.add_output(output); + block_template.body.add_kernel(kernel); + Ok(block_template.into()) } diff --git a/applications/tari_merge_mining_proxy/src/error.rs b/applications/tari_merge_mining_proxy/src/error.rs index 37afa6d879..7affa3a6e3 100644 --- a/applications/tari_merge_mining_proxy/src/error.rs +++ b/applications/tari_merge_mining_proxy/src/error.rs @@ -75,10 +75,10 @@ pub enum MmProxyError { CoinbaseBuilderError(#[from] CoinbaseBuildError), #[error("Unexpected Tari base node response: {0}")] UnexpectedTariBaseNodeResponse(String), - #[error("Invalid RandomX seed")] - InvalidRandomXSeed, #[error("Invalid header value")] InvalidHeaderValue(#[from] InvalidHeaderValue), + #[error("Block was lost due to a failed precondition, and should be retried")] + FailedPreconditionBlockLostRetry, } impl From for MmProxyError { diff --git a/applications/tari_merge_mining_proxy/src/main.rs b/applications/tari_merge_mining_proxy/src/main.rs index dd73207c8a..7e15777977 100644 --- a/applications/tari_merge_mining_proxy/src/main.rs +++ b/applications/tari_merge_mining_proxy/src/main.rs @@ -28,6 +28,7 @@ #![deny(unknown_lints)] mod block_template_data; +mod block_template_protocol; mod common; mod error; mod proxy; @@ -57,8 +58,10 @@ async fn main() -> Result<(), anyhow::Error> { .pool_max_idle_per_host(25) .build() .map_err(MmProxyError::ReqwestError)?; + println!("Connecting to base node at {}", config.grpc_base_node_address); let base_node_client = grpc::base_node_client::BaseNodeClient::connect(format!("http://{}", config.grpc_base_node_address)).await?; + println!("Connecting to wallet at {}", config.grpc_console_wallet_address); let wallet_client = grpc::wallet_client::WalletClient::connect(format!("http://{}", config.grpc_console_wallet_address)).await?; let xmrig_service = MergeMiningProxyService::new( diff --git a/applications/tari_merge_mining_proxy/src/proxy.rs b/applications/tari_merge_mining_proxy/src/proxy.rs index cf4615f3e9..7d4356ed6f 100644 --- a/applications/tari_merge_mining_proxy/src/proxy.rs +++ b/applications/tari_merge_mining_proxy/src/proxy.rs @@ -21,8 +21,9 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use crate::{ - block_template_data::{BlockTemplateDataBuilder, BlockTemplateRepository}, - common::{json_rpc, merge_mining, monero_rpc::CoreRpcErrorCode, proxy, proxy::convert_json_to_hyper_json_response}, + block_template_data::BlockTemplateRepository, + block_template_protocol::{BlockTemplateProtocol, MoneroMiningData}, + common::{json_rpc, monero_rpc::CoreRpcErrorCode, proxy, proxy::convert_json_to_hyper_json_response}, error::MmProxyError, }; use bytes::Bytes; @@ -33,8 +34,6 @@ use reqwest::{ResponseBuilderExt, Url}; use serde_json as json; use std::{ cmp, - cmp::min, - convert::TryFrom, future::Future, net::SocketAddr, pin::Pin, @@ -45,12 +44,9 @@ use std::{ task::{Context, Poll}, time::Instant, }; -use tari_app_grpc::{tari_rpc as grpc, tari_rpc::GetCoinbaseRequest}; +use tari_app_grpc::tari_rpc as grpc; use tari_common::{configuration::Network, GlobalConfig}; -use tari_core::{ - blocks::{Block, NewBlockTemplate}, - proof_of_work::monero_rx, -}; +use tari_core::proof_of_work::{monero_rx, monero_rx::FixedByteArray}; use tari_utilities::hex::Hex; use tracing::{debug, error, info, instrument, trace, warn}; @@ -127,14 +123,32 @@ impl Service> for MergeMiningProxyService { Poll::Ready(Ok(())) } - fn call(&mut self, req: Request) -> Self::Future { + fn call(&mut self, mut request: Request) -> Self::Future { let inner = self.inner.clone(); let future = async move { - match inner.handle(req).await { + let bytes = match proxy::read_body_until_end(request.body_mut()).await { + Ok(b) => b, + Err(err) => { + eprintln!("Method: Unknown, Failed to read request: {}", err); + let resp = proxy::json_response( + StatusCode::BAD_REQUEST, + &json_rpc::standard_error_response( + None, + StandardError::InvalidRequest, + Some(json!({"details": err.to_string()})), + ), + ) + .expect("unexpected failure"); + return Ok(resp); + }, + }; + let request = request.map(|_| bytes.freeze()); + let method_name = parse_method_name(&request); + match inner.handle(&method_name, request).await { Ok(resp) => Ok(resp), Err(err) => { error!(target: LOG_TARGET, "Error handling request: {}", err); - + eprintln!("Method: {}, Failed to handle request: {}", method_name, err); Ok(proxy::json_response( StatusCode::INTERNAL_SERVER_ERROR, &json_rpc::standard_error_response( @@ -360,53 +374,36 @@ impl InnerService { } let mut grpc_client = self.base_node_client.clone(); + let mut grpc_wallet_client = self.wallet_client.clone(); // Add merge mining tag on blocktemplate request debug!(target: LOG_TARGET, "Requested new block template from Tari base node"); - - let grpc::NewBlockTemplateResponse { - miner_data, - new_block_template, - initial_sync_achieved, - } = grpc_client - .get_new_block_template(grpc::NewBlockTemplateRequest { - algo: Some(grpc::PowAlgo { - pow_algo: grpc::pow_algo::PowAlgos::Monero.into(), - }), - max_weight: 0, - }) - .await - .map_err(|status| MmProxyError::GrpcRequestError { - status, - details: "failed to get new block template".to_string(), - })? - .into_inner(); - - let miner_data = miner_data.ok_or(MmProxyError::GrpcResponseMissingField("miner_data"))?; - let new_block_template = - new_block_template.ok_or(MmProxyError::GrpcResponseMissingField("new_block_template"))?; - - let block_reward = miner_data.reward; - let total_fees = miner_data.total_fees; - let tari_difficulty = miner_data.target_difficulty; - if !self.initial_sync_achieved.load(Ordering::Relaxed) { + let grpc::TipInfoResponse { + initial_sync_achieved, + metadata, + .. + } = grpc_client + .get_tip_info(tari_app_grpc::tari_rpc::Empty {}) + .await? + .into_inner(); + if !initial_sync_achieved { let msg = format!( "Initial base node sync not achieved, current height at #{} ... (waiting = {})", - new_block_template.header.as_ref().map(|h| h.height).unwrap_or_default(), + metadata.as_ref().map(|h| h.height_of_longest_chain).unwrap_or_default(), self.config.wait_for_initial_sync_at_startup, ); debug!(target: LOG_TARGET, "{}", msg); println!("{}", msg); if self.config.wait_for_initial_sync_at_startup { - return Err(MmProxyError::MissingDataError(" ".to_string() + &msg)); + return Err(MmProxyError::MissingDataError(msg)); } } else { self.initial_sync_achieved.store(true, Ordering::Relaxed); let msg = format!( "Initial base node sync achieved. Ready to mine at height #{}", - new_block_template.header.as_ref().map(|h| h.height).unwrap_or_default() + metadata.as_ref().map(|h| h.height_of_longest_chain).unwrap_or_default(), ); debug!(target: LOG_TARGET, "{}", msg); println!("{}", msg); @@ -414,103 +411,45 @@ impl InnerService { } } - info!( - target: LOG_TARGET, - "Received new block template from Tari base node for height #{}", - new_block_template.header.as_ref().map(|h| h.height).unwrap_or_default(), - ); - - let template_block = NewBlockTemplate::try_from(new_block_template) - .map_err(|e| MmProxyError::MissingDataError(format!("GRPC Conversion Error: {}", e)))?; - let tari_height = template_block.header.height; - - debug!(target: LOG_TARGET, "Trying to connect to wallet"); - let mut grpc_wallet_client = self.wallet_client.clone(); - let coinbase_response = grpc_wallet_client - .get_coinbase(GetCoinbaseRequest { - reward: block_reward, - fee: total_fees, - height: tari_height, - }) - .await - .map_err(|status| MmProxyError::GrpcRequestError { - status, - details: "failed to get new block template".to_string(), - })?; - let coinbase_transaction = coinbase_response.into_inner().transaction; - - let coinbased_block = merge_mining::add_coinbase(coinbase_transaction, template_block)?; - debug!(target: LOG_TARGET, "Added coinbase to new block template"); - let block = grpc_client - .get_new_block(coinbased_block) - .await - .map_err(|status| MmProxyError::GrpcRequestError { - status, - details: "failed to get new block".to_string(), - })? - .into_inner(); - - let mining_hash = block.merge_mining_hash; - - let tari_block = Block::try_from( - block - .block - .clone() - .ok_or_else(|| MmProxyError::MissingDataError("Tari block".to_string()))?, - ) - .map_err(MmProxyError::MissingDataError)?; - debug!(target: LOG_TARGET, "New block received from Tari: {}", (tari_block)); + let new_block_protocol = BlockTemplateProtocol::new(&mut grpc_client, &mut grpc_wallet_client); - let block_data = BlockTemplateDataBuilder::default(); - let block_data = block_data - .tari_block(block.block.ok_or(MmProxyError::GrpcResponseMissingField("block"))?) - .tari_miner_data(miner_data); - - // Deserialize the block template blob - let block_template_blob = &monerod_resp["result"]["blocktemplate_blob"] + 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)))?; + let blocktemplate_blob = monerod_resp["result"]["blocktemplate_blob"] .to_string() .replace("\"", ""); - debug!(target: LOG_TARGET, "Deserializing Blocktemplate Blob into Monero Block",); - let mut monero_block = monero_rx::deserialize_monero_block_from_hex(block_template_blob)?; - - debug!(target: LOG_TARGET, "Appending Merged Mining Tag",); - // Add the Tari merge mining tag to the retrieved block template - monero_rx::append_merge_mining_tag(&mut monero_block, &mining_hash)?; - - debug!(target: LOG_TARGET, "Creating blockhashing blob from blocktemplate blob",); - // Must be done after the tag is inserted since it will affect the hash of the miner tx - let blockhashing_blob = monero_rx::create_blockhashing_blob_from_block(&monero_block)?; - - debug!(target: LOG_TARGET, "blockhashing_blob:{}", blockhashing_blob); - monerod_resp["result"]["blockhashing_blob"] = blockhashing_blob.into(); - - let blocktemplate_blob = monero_rx::serialize_monero_block_to_hex(&monero_block)?; - debug!(target: LOG_TARGET, "blocktemplate_blob:{}", block_template_blob); - monerod_resp["result"]["blocktemplate_blob"] = blocktemplate_blob.into(); - - let seed = monerod_resp["result"]["seed_hash"].to_string().replace("\"", ""); - - let block_data = block_data.monero_seed(seed); - - let monero_difficulty = monerod_resp["result"]["difficulty"].as_u64().unwrap_or_default(); + let difficulty = monerod_resp["result"]["difficulty"].as_u64().unwrap_or_default(); + let monero_mining_data = MoneroMiningData { + seed_hash, + blocktemplate_blob, + difficulty, + }; - let mining_difficulty = min(monero_difficulty, tari_difficulty); + let final_block_template_data = new_block_protocol.get_next_block_template(monero_mining_data).await?; - let block_data = block_data - .monero_difficulty(monero_difficulty) - .tari_difficulty(tari_difficulty); + monerod_resp["result"]["blocktemplate_blob"] = final_block_template_data.blocktemplate_blob.into(); + monerod_resp["result"]["blockhashing_blob"] = final_block_template_data.blockhashing_blob.into(); + monerod_resp["result"]["difficulty"] = final_block_template_data.target_difficulty.as_u64().into(); - info!( - target: LOG_TARGET, - "Difficulties: Tari ({}), Monero({}), Selected({})", tari_difficulty, monero_difficulty, mining_difficulty + let tari_height = final_block_template_data + .template + .tari_block + .header + .as_ref() + .map(|h| h.height) + .unwrap_or(0); + let block_reward = final_block_template_data.template.tari_miner_data.reward; + let total_fees = final_block_template_data.template.tari_miner_data.total_fees; + let mining_hash = final_block_template_data.merge_mining_hash; + let monerod_resp = add_aux_data( + monerod_resp, + json!({ "base_difficulty": final_block_template_data.template.monero_difficulty }), ); - monerod_resp["result"]["difficulty"] = mining_difficulty.into(); - let monerod_resp = add_aux_data(monerod_resp, json!({ "base_difficulty": monero_difficulty })); let monerod_resp = append_aux_chain_data( monerod_resp, json!({ "id": TARI_CHAIN_ID, - "difficulty": tari_difficulty, + "difficulty": final_block_template_data.template.tari_difficulty, "height": tari_height, // The merge mining hash, before the final block hash can be calculated "mining_hash": mining_hash.to_hex(), @@ -518,7 +457,9 @@ impl InnerService { }), ); - self.block_templates.save(mining_hash, block_data.build()?).await; + self.block_templates + .save(mining_hash, final_block_template_data.template) + .await; debug!(target: LOG_TARGET, "Returning template result: {}", monerod_resp); Ok(proxy::into_response(parts, &monerod_resp)) @@ -780,25 +721,8 @@ impl InnerService { } } - async fn handle(self, mut request: Request) -> Result, MmProxyError> { + async fn handle(self, method_name: &str, request: Request) -> Result, MmProxyError> { let start = Instant::now(); - let bytes = proxy::read_body_until_end(request.body_mut()).await?; - let request = request.map(|_| bytes.freeze()); - let method_name; - match *request.method() { - Method::GET => { - let mut chars = request.uri().path().chars(); - chars.next(); - method_name = chars.as_str().to_string(); - }, - Method::POST => { - let json = json::from_slice::(request.body()).unwrap_or_default(); - method_name = str::replace(json["method"].as_str().unwrap_or_default(), "\"", ""); - }, - _ => { - method_name = "unsupported".to_string(); - }, - } debug!( target: LOG_TARGET, @@ -935,3 +859,18 @@ fn try_into_json_block_header(header: grpc::BlockHeaderResponse) -> Result) -> String { + match *request.method() { + Method::GET => { + let mut chars = request.uri().path().chars(); + chars.next(); + chars.as_str().to_string() + }, + Method::POST => { + let json = json::from_slice::(request.body()).unwrap_or_default(); + str::replace(json["method"].as_str().unwrap_or_default(), "\"", "") + }, + _ => "unsupported".to_string(), + } +} diff --git a/base_layer/core/src/chain_storage/blockchain_database.rs b/base_layer/core/src/chain_storage/blockchain_database.rs index 2df7cece52..84812820d5 100644 --- a/base_layer/core/src/chain_storage/blockchain_database.rs +++ b/base_layer/core/src/chain_storage/blockchain_database.rs @@ -923,9 +923,8 @@ pub fn calculate_mmr_roots(db: &T, block: &Block) -> Resul let metadata = db.fetch_chain_metadata()?; if header.prev_hash != *metadata.best_block() { - return Err(ChainStorageError::InvalidOperation(format!( - "Cannot calculate MMR roots for block that does not form a chain with the current tip. Block (#{}) \ - previous hash is {} but the current tip is #{} {}", + return Err(ChainStorageError::CannotCalculateNonTipMmr(format!( + "Block (#{}) previous hash is {} but the current tip is #{} {}", header.height, header.prev_hash.to_hex(), metadata.height_of_longest_chain(), diff --git a/base_layer/core/src/chain_storage/error.rs b/base_layer/core/src/chain_storage/error.rs index a27b47f8e3..f8541071fc 100644 --- a/base_layer/core/src/chain_storage/error.rs +++ b/base_layer/core/src/chain_storage/error.rs @@ -105,6 +105,8 @@ pub enum ChainStorageError { CannotAcquireFileLock, #[error("IO Error: `{0}`")] IoError(#[from] std::io::Error), + #[error("Cannot calculate MMR roots for block that does not form a chain with the current tip. {0}")] + CannotCalculateNonTipMmr(String), } impl ChainStorageError { diff --git a/integration_tests/features/WalletBaseNodeSwitch.feature b/integration_tests/features/WalletBaseNodeSwitch.feature index 873cb5c71f..403b35f674 100644 --- a/integration_tests/features/WalletBaseNodeSwitch.feature +++ b/integration_tests/features/WalletBaseNodeSwitch.feature @@ -1,9 +1,10 @@ Feature: Wallet Base Node Switch + @doit Scenario: As a user I want to change base node for a wallet Given I have a base node Node1 connected to all seed nodes And I have a base node Node2 connected to all seed nodes And I have wallet Wallet connected to base node Node1 When I stop wallet Wallet And change base node of Wallet to Node2 - Then Wallet is connected to Node2 + Then I wait for Wallet to connect to Node2 diff --git a/integration_tests/helpers/miningNodeProcess.js b/integration_tests/helpers/miningNodeProcess.js index 170b3c883a..faf05c7429 100644 --- a/integration_tests/helpers/miningNodeProcess.js +++ b/integration_tests/helpers/miningNodeProcess.js @@ -115,7 +115,7 @@ class MiningNodeProcess { "--base-path", ".", "--init", - "--daemon", + "--non-interactive", "--max-blocks", this.maxBlocks, "--mine-until-height", diff --git a/integration_tests/helpers/walletProcess.js b/integration_tests/helpers/walletProcess.js index e66d23f08a..80c291c407 100644 --- a/integration_tests/helpers/walletProcess.js +++ b/integration_tests/helpers/walletProcess.js @@ -220,7 +220,7 @@ class WalletProcess { "kensentme", "--command", `set-base-node ${baseNode}`, - "--daemon", + "--non-interactive", ]; if (this.logFilePath) { args.push("--log-config", this.logFilePath);