From 6723dc7a88b2c1e40efe51259cb26e12638b9668 Mon Sep 17 00:00:00 2001 From: SW van Heerden Date: Wed, 29 Nov 2023 21:30:52 +0200 Subject: [PATCH] feat!: add aux chain support for merge mining (#5976) Description --- This adds aux chain support for merge mining monero Fixes: #5975 Motivation and Context --- This allows tari to be merged mined with multiple other chains, up to 253 other chains. See: https://github.com/SChernykh/p2pool/blob/merge-mining/docs/MERGE_MINING.MD#proposed-rpc-api How Has This Been Tested? --- --- Cargo.lock | 32 +- .../minotari_merge_mining_proxy/Cargo.toml | 1 + .../src/block_template_data.rs | 28 +- .../src/block_template_protocol.rs | 32 +- .../minotari_merge_mining_proxy/src/proxy.rs | 39 ++- .../src/run_merge_miner.rs | 2 +- base_layer/core/Cargo.toml | 2 + .../comms_interface/inbound_handlers.rs | 3 +- .../core/src/proof_of_work/monero_rx/error.rs | 4 +- .../src/proof_of_work/monero_rx/helpers.rs | 177 ++++++++--- .../proof_of_work/monero_rx/merkle_tree.rs | 255 +++++++++++++--- .../monero_rx/merkle_tree_parameters.rs | 278 ++++++++++++++++++ .../core/src/proof_of_work/monero_rx/mod.rs | 6 +- .../src/proof_of_work/monero_rx/pow_data.rs | 10 +- .../src/validation/difficulty_calculator.rs | 3 +- .../header/header_full_validator.rs | 3 +- base_layer/core/src/validation/helpers.rs | 4 +- .../core/tests/tests/block_validation.rs | 12 +- 18 files changed, 772 insertions(+), 119 deletions(-) create mode 100644 base_layer/core/src/proof_of_work/monero_rx/merkle_tree_parameters.rs diff --git a/Cargo.lock b/Cargo.lock index b37847c528..2537059b88 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1797,6 +1797,16 @@ dependencies = [ "termcolor", ] +[[package]] +name = "env_logger" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "log", + "regex", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -3154,6 +3164,7 @@ dependencies = [ "minotari_app_utilities", "minotari_node_grpc_client", "minotari_wallet_grpc_client", + "monero", "reqwest", "serde", "serde_json", @@ -3293,7 +3304,7 @@ dependencies = [ "diesel", "diesel_migrations", "digest 0.10.7", - "env_logger", + "env_logger 0.7.1", "fs2", "futures 0.3.29", "itertools 0.10.5", @@ -4421,6 +4432,17 @@ dependencies = [ "memchr", ] +[[package]] +name = "quickcheck" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" +dependencies = [ + "env_logger 0.8.4", + "log", + "rand", +] + [[package]] name = "quote" version = "1.0.33" @@ -5600,7 +5622,7 @@ dependencies = [ "data-encoding", "derivative", "digest 0.10.7", - "env_logger", + "env_logger 0.7.1", "futures 0.3.29", "lmdb-zero", "log", @@ -5649,7 +5671,7 @@ dependencies = [ "diesel", "diesel_migrations", "digest 0.10.7", - "env_logger", + "env_logger 0.7.1", "futures 0.3.29", "futures-test", "futures-util", @@ -5744,7 +5766,7 @@ dependencies = [ "decimal-rs", "derivative", "digest 0.10.7", - "env_logger", + "env_logger 0.7.1", "fs2", "futures 0.3.29", "hex", @@ -5761,11 +5783,13 @@ dependencies = [ "once_cell", "primitive-types", "prost 0.9.0", + "quickcheck", "rand", "randomx-rs", "serde", "serde_json", "serde_repr", + "sha2 0.10.8", "sha3", "strum", "strum_macros", diff --git a/applications/minotari_merge_mining_proxy/Cargo.toml b/applications/minotari_merge_mining_proxy/Cargo.toml index a4886f2f1d..27b8c90a37 100644 --- a/applications/minotari_merge_mining_proxy/Cargo.toml +++ b/applications/minotari_merge_mining_proxy/Cargo.toml @@ -35,6 +35,7 @@ hex = "0.4.2" hyper = "0.14.12" jsonrpc = "0.12.0" log = { version = "0.4.8", features = ["std"] } +monero = { version = "0.18" } reqwest = { version = "0.11.4", features = ["json"] } serde = { version = "1.0.106", features = ["derive"] } serde_json = "1.0.57" diff --git a/applications/minotari_merge_mining_proxy/src/block_template_data.rs b/applications/minotari_merge_mining_proxy/src/block_template_data.rs index 3ee6c19350..d88598dda4 100644 --- a/applications/minotari_merge_mining_proxy/src/block_template_data.rs +++ b/applications/minotari_merge_mining_proxy/src/block_template_data.rs @@ -28,6 +28,7 @@ use std::{collections::HashMap, sync::Arc}; use chrono::Duration; use chrono::{self, DateTime, Utc}; use minotari_node_grpc_client::grpc; +use tari_common_types::types::FixedHash; use tari_core::proof_of_work::monero_rx::FixedByteArray; use tokio::sync::RwLock; use tracing::trace; @@ -125,6 +126,8 @@ pub struct BlockTemplateData { pub tari_miner_data: grpc::MinerData, pub monero_difficulty: u64, pub tari_difficulty: u64, + pub tari_hash: FixedHash, + pub aux_chain_hashes: Vec, } impl BlockTemplateData {} @@ -137,6 +140,8 @@ pub struct BlockTemplateDataBuilder { tari_miner_data: Option, monero_difficulty: Option, tari_difficulty: Option, + tari_hash: Option, + aux_chain_hashes: Vec, } impl BlockTemplateDataBuilder { @@ -169,6 +174,16 @@ impl BlockTemplateDataBuilder { self } + pub fn tari_hash(mut self, hash: FixedHash) -> Self { + self.tari_hash = Some(hash); + self + } + + pub fn aux_hashes(mut self, aux_chain_hashes: Vec) -> Self { + self.aux_chain_hashes = aux_chain_hashes; + self + } + /// Build a new [BlockTemplateData], all the values have to be set. /// /// # Errors @@ -190,6 +205,12 @@ impl BlockTemplateDataBuilder { let tari_difficulty = self .tari_difficulty .ok_or_else(|| MmProxyError::MissingDataError("tari_difficulty not provided".to_string()))?; + let tari_hash = self + .tari_hash + .ok_or_else(|| MmProxyError::MissingDataError("tari_hash not provided".to_string()))?; + if self.aux_chain_hashes.is_empty() { + return Err(MmProxyError::MissingDataError("aux chain hashes are empty".to_string())); + }; Ok(BlockTemplateData { monero_seed, @@ -197,6 +218,8 @@ impl BlockTemplateDataBuilder { tari_miner_data, monero_difficulty, tari_difficulty, + tari_hash, + aux_chain_hashes: self.aux_chain_hashes, }) } } @@ -216,6 +239,7 @@ pub mod test { let header = BlockHeader::new(100); let body = AggregateBody::empty(); let block = Block::new(header, body); + let hash = block.hash(); let miner_data = grpc::MinerData { reward: 10000, target_difficulty: 600000, @@ -227,7 +251,9 @@ pub mod test { .tari_block(block.try_into().unwrap()) .tari_miner_data(miner_data) .monero_difficulty(123456) - .tari_difficulty(12345); + .tari_difficulty(12345) + .tari_hash(hash) + .aux_hashes(vec![monero::Hash::from_slice(hash.as_slice())]); btdb.build().unwrap() } 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 8cd1d6496c..9d62a85891 100644 --- a/applications/minotari_merge_mining_proxy/src/block_template_protocol.rs +++ b/applications/minotari_merge_mining_proxy/src/block_template_protocol.rs @@ -21,13 +21,12 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //! Methods for seting up a new block. - -use std::{cmp, str::FromStr, sync::Arc}; +use std::{cmp, convert::TryFrom, str::FromStr, sync::Arc}; use log::*; use minotari_app_grpc::{authentication::ClientAuthenticationInterceptor, tari_rpc::base_node_client::BaseNodeClient}; use minotari_node_grpc_client::grpc; -use tari_common_types::tari_address::TariAddress; +use tari_common_types::{tari_address::TariAddress, types::FixedHash}; use tari_core::{ consensus::ConsensusManager, proof_of_work::{monero_rx, monero_rx::FixedByteArray, Difficulty}, @@ -61,11 +60,11 @@ impl<'a> BlockTemplateProtocol<'a> { pub async fn new( base_node_client: &'a mut BaseNodeClient>, config: Arc, + consensus_manager: ConsensusManager, ) -> 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()))?; - let consensus_manager = ConsensusManager::builder(config.network).build()?; Ok(Self { config, base_node_client, @@ -242,6 +241,11 @@ impl BlockTemplateProtocol<'_> { .monero_seed(monero_mining_data.seed_hash) .monero_difficulty(monero_mining_data.difficulty) .tari_difficulty(tari_difficulty) + .tari_hash( + FixedHash::try_from(tari_block.merge_mining_hash.clone()) + .map_err(|e| MmProxyError::MissingDataError(e.to_string()))?, + ) + .aux_hashes(vec![monero::Hash::from_slice(&tari_block.merge_mining_hash)]) .build()?; // Deserialize the block template blob @@ -250,7 +254,15 @@ impl BlockTemplateProtocol<'_> { debug!(target: LOG_TARGET, "Insert Merged Mining Tag",); // Add the Tari merge mining tag to the retrieved block template - monero_rx::insert_merge_mining_tag_into_block(&mut monero_block, &tari_block.merge_mining_hash)?; + // We need to send the MR al all aux chains, but a single chain, aka minotari only, means we only need the tari + // hash + let aux_chain_mr = tari_block.merge_mining_hash.clone(); + monero_rx::insert_merge_mining_tag_and_aux_chain_merkle_root_into_block( + &mut monero_block, + &aux_chain_mr, + 1, + 0, + )?; 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 @@ -266,12 +278,16 @@ impl BlockTemplateProtocol<'_> { monero_mining_data.difficulty, mining_difficulty ); + let merge_mining_hash = FixedHash::try_from(tari_block.merge_mining_hash.clone()) + .map_err(|e| MmProxyError::MissingDataError(e.to_string()))?; Ok(FinalBlockTemplateData { template: block_template_data, target_difficulty: Difficulty::from_u64(mining_difficulty)?, blockhashing_blob, blocktemplate_blob, - merge_mining_hash: tari_block.merge_mining_hash, + merge_mining_hash, + aux_chain_hashes: vec![monero::Hash::from_slice(&tari_block.merge_mining_hash)], + aux_chain_mr: tari_block.merge_mining_hash, }) } } @@ -296,7 +312,9 @@ pub struct FinalBlockTemplateData { pub target_difficulty: Difficulty, pub blockhashing_blob: String, pub blocktemplate_blob: String, - pub merge_mining_hash: Vec, + pub merge_mining_hash: FixedHash, + pub aux_chain_hashes: Vec, + pub aux_chain_mr: Vec, } /// Container struct for monero mining data inputs obtained from monerod diff --git a/applications/minotari_merge_mining_proxy/src/proxy.rs b/applications/minotari_merge_mining_proxy/src/proxy.rs index bde3e7917b..3f062d18c7 100644 --- a/applications/minotari_merge_mining_proxy/src/proxy.rs +++ b/applications/minotari_merge_mining_proxy/src/proxy.rs @@ -43,11 +43,9 @@ use minotari_node_grpc_client::{grpc, grpc::base_node_client::BaseNodeClient}; use minotari_wallet_grpc_client::ClientAuthenticationInterceptor; use reqwest::{ResponseBuilderExt, Url}; use serde_json as json; -use tari_core::proof_of_work::{ - monero_rx, - monero_rx::FixedByteArray, - randomx_difficulty, - randomx_factory::RandomXFactory, +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}; @@ -79,9 +77,10 @@ impl MergeMiningProxyService { base_node_client: BaseNodeClient>, block_templates: BlockTemplateRepository, randomx_factory: RandomXFactory, - ) -> Self { + ) -> Result { debug!(target: LOG_TARGET, "Config: {:?}", config); - Self { + let consensus_manager = ConsensusManager::builder(config.network).build()?; + Ok(Self { inner: InnerService { config: Arc::new(config), block_templates, @@ -91,8 +90,9 @@ impl MergeMiningProxyService { current_monerod_server: Arc::new(RwLock::new(None)), last_assigned_monerod_server: Arc::new(RwLock::new(None)), randomx_factory, + consensus_manager, }, - } + }) } } @@ -161,6 +161,7 @@ struct InnerService { current_monerod_server: Arc>>, last_assigned_monerod_server: Arc>>, randomx_factory: RandomXFactory, + consensus_manager: ConsensusManager, } impl InnerService { @@ -238,10 +239,12 @@ impl InnerService { }, }; + let gen_hash = *self.consensus_manager.get_genesis_block().hash(); + for param in params.iter().filter_map(|p| p.as_str()) { let monero_block = monero_rx::deserialize_monero_block_from_hex(param)?; debug!(target: LOG_TARGET, "Monero block: {}", monero_block); - let hash = monero_rx::extract_tari_hash_from_block(&monero_block)?.ok_or_else(|| { + let hash = monero_rx::extract_aux_merkle_root_from_block(&monero_block)?.ok_or_else(|| { MmProxyError::MissingDataError("Could not find Minotari header in coinbase".to_string()) })?; @@ -262,8 +265,12 @@ impl InnerService { continue; }, }; - - let monero_data = monero_rx::construct_monero_data(monero_block, block_data.monero_seed.clone())?; + let monero_data = monero_rx::construct_monero_data( + monero_block, + block_data.monero_seed.clone(), + block_data.aux_chain_hashes.clone(), + block_data.tari_hash, + )?; debug!(target: LOG_TARGET, "Monero PoW Data: {:?}", monero_data); @@ -276,7 +283,7 @@ impl InnerService { let start = Instant::now(); let achieved_target = if self.config.check_tari_difficulty_before_submit { trace!(target: LOG_TARGET, "Starting calculate achieved Tari difficultly"); - let diff = randomx_difficulty(&tari_header, &self.randomx_factory)?; + let diff = randomx_difficulty(&tari_header, &self.randomx_factory, &gen_hash)?; trace!( target: LOG_TARGET, "Finished calculate achieved Tari difficultly - achieved {} vs. target {}", @@ -425,7 +432,8 @@ impl InnerService { } } - let new_block_protocol = BlockTemplateProtocol::new(&mut grpc_client, self.config.clone()).await?; + let new_block_protocol = + BlockTemplateProtocol::new(&mut grpc_client, self.config.clone(), self.consensus_manager.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)))?; @@ -472,7 +480,10 @@ impl InnerService { ); self.block_templates - .save(mining_hash, final_block_template_data.template) + .save( + final_block_template_data.aux_chain_mr, + final_block_template_data.template, + ) .await; debug!(target: LOG_TARGET, "Returning template result: {}", monerod_resp); 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 74a97556e7..2966bce7d9 100644 --- a/applications/minotari_merge_mining_proxy/src/run_merge_miner.rs +++ b/applications/minotari_merge_mining_proxy/src/run_merge_miner.rs @@ -87,7 +87,7 @@ pub async fn start_merge_miner(cli: Cli) -> Result<(), anyhow::Error> { base_node_client, BlockTemplateRepository::new(), randomx_factory, - ); + )?; let service = make_service_fn(|_conn| future::ready(Result::<_, Infallible>::Ok(randomx_service.clone()))); match Server::try_bind(&listen_addr) { diff --git a/base_layer/core/Cargo.toml b/base_layer/core/Cargo.toml index c02977b202..5a0d9c7c39 100644 --- a/base_layer/core/Cargo.toml +++ b/base_layer/core/Cargo.toml @@ -68,6 +68,7 @@ serde = { version = "1.0.106", features = ["derive"] } serde_json = "1.0" serde_repr = "0.1.8" sha3 = "0.10" +sha2 = "0.10" strum = "0.22" strum_macros = "0.22" thiserror = "1.0.26" @@ -87,6 +88,7 @@ config = { version = "0.13.0" } env_logger = "0.7.0" tempfile = "3.1.0" toml = { version = "0.5" } +quickcheck = "1.0" [build-dependencies] tari_common = { path = "../../common", features = ["build"] } diff --git a/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs b/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs index c7f598429b..8077c6db73 100644 --- a/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs +++ b/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs @@ -501,6 +501,7 @@ where B: BlockchainBackend + 'static async fn check_min_block_difficulty(&self, new_block: &NewBlock) -> Result<(), CommsInterfaceError> { let constants = self.consensus_manager.consensus_constants(new_block.header.height); + let gen_hash = *self.consensus_manager.get_genesis_block().hash(); let mut min_difficulty = constants.min_pow_difficulty(new_block.header.pow.pow_algo); let mut header = self.blockchain_db.fetch_last_chain_header().await?; loop { @@ -525,7 +526,7 @@ where B: BlockchainBackend + 'static .await?; } let achieved = match new_block.header.pow_algo() { - PowAlgorithm::RandomX => randomx_difficulty(&new_block.header, &self.randomx_factory)?, + PowAlgorithm::RandomX => randomx_difficulty(&new_block.header, &self.randomx_factory, &gen_hash)?, PowAlgorithm::Sha3x => sha3x_difficulty(&new_block.header)?, }; if achieved < min_difficulty { diff --git a/base_layer/core/src/proof_of_work/monero_rx/error.rs b/base_layer/core/src/proof_of_work/monero_rx/error.rs index ab7d7958e5..1517ff9f0f 100644 --- a/base_layer/core/src/proof_of_work/monero_rx/error.rs +++ b/base_layer/core/src/proof_of_work/monero_rx/error.rs @@ -48,6 +48,8 @@ pub enum MergeMineError { InvalidMerkleRoot, #[error("Invalid difficulty: {0}")] DifficultyError(#[from] DifficultyError), + #[error("Cannot mine with 0 aux chains")] + ZeroAuxChains, } impl MergeMineError { @@ -64,7 +66,7 @@ impl MergeMineError { reason: err.to_string(), ban_duration: BanPeriod::Long, }), - MergeMineError::RandomXVMFactoryError(_) => None, + MergeMineError::RandomXVMFactoryError(_) | MergeMineError::ZeroAuxChains => None, } } } diff --git a/base_layer/core/src/proof_of_work/monero_rx/helpers.rs b/base_layer/core/src/proof_of_work/monero_rx/helpers.rs index e3b130a864..1e0212f722 100644 --- a/base_layer/core/src/proof_of_work/monero_rx/helpers.rs +++ b/base_layer/core/src/proof_of_work/monero_rx/helpers.rs @@ -28,6 +28,9 @@ use monero::{ cryptonote::hash::Hashable, VarInt, }; +use primitive_types::U256; +use sha2::{Digest, Sha256}; +use tari_common_types::types::FixedHash; use tari_utilities::hex::HexError; use super::{ @@ -39,6 +42,7 @@ use super::{ use crate::{ blocks::BlockHeader, proof_of_work::{ + monero_rx::merkle_tree_parameters::MerkleTreeParameters, randomx_factory::{RandomXFactory, RandomXVMInstance}, Difficulty, }, @@ -50,8 +54,9 @@ pub const LOG_TARGET: &str = "c::pow::monero_rx"; pub fn randomx_difficulty( header: &BlockHeader, randomx_factory: &RandomXFactory, + gen_hash: &FixedHash, ) -> Result { - let monero_pow_data = verify_header(header)?; + let monero_pow_data = verify_header(header, gen_hash)?; debug!(target: LOG_TARGET, "Valid Monero data: {}", monero_pow_data); let blockhashing_blob = monero_pow_data.to_blockhashing_blob(); let vm = randomx_factory.create(monero_pow_data.randomx_key())?; @@ -89,7 +94,7 @@ fn parse_extra_field_truncate_on_error(raw_extra_field: &RawExtraField) -> Extra /// 1. The merkle proof and coinbase hash produce a matching merkle root /// /// If these assertions pass, a valid `MoneroPowData` instance is returned -pub fn verify_header(header: &BlockHeader) -> Result { +pub fn verify_header(header: &BlockHeader, gen_hash: &FixedHash) -> Result { let monero_data = MoneroPowData::from_header(header)?; let expected_merge_mining_hash = header.merge_mining_hash(); let extra_field = ExtraField::try_parse(&monero_data.coinbase_tx.prefix.extra) @@ -107,9 +112,13 @@ pub fn verify_header(header: &BlockHeader) -> Result Result bool { + let t_hash = monero::Hash::from_slice(tari_hash.as_slice()); + if merge_mining_params == VarInt(0) { + // we interpret 0 as there is only 1 chain, tari. + if t_hash == *aux_chain_merkle_root { + return true; + } + } + let merkle_tree_params = MerkleTreeParameters::from_varint(merge_mining_params); + if merkle_tree_params.number_of_chains == 0 { + return false; + } + let hash_position = U256::from_little_endian( + &Sha256::new() + .chain_update(tari_genesis_block_hash) + .chain_update(merkle_tree_params.aux_nonce.to_le_bytes()) + .chain_update((109_u8).to_le_bytes()) + .finalize(), + ) + .low_u32() % + u32::from(merkle_tree_params.number_of_chains); + let (merkle_root, pos) = monero_data.aux_chain_merkle_proof.calculate_root_with_pos(&t_hash); + if hash_position != pos { + return false; + } + + merkle_root == *aux_chain_merkle_root +} + /// Extracts the Monero block hash from the coinbase transaction's extra field -pub fn extract_tari_hash_from_block(monero: &monero::Block) -> Result, MergeMineError> { +pub fn extract_aux_merkle_root_from_block(monero: &monero::Block) -> Result, MergeMineError> { // When we extract the merge mining hash, we do not care if the extra field can be parsed without error. let extra_field = parse_extra_field_truncate_on_error(&monero.miner_tx.prefix.extra); @@ -173,16 +217,28 @@ pub fn serialize_monero_block_to_hex(obj: &monero::Block) -> Result Result { +pub fn construct_monero_data( + block: monero::Block, + seed: FixedByteArray, + ordered_aux_chain_hashes: Vec, + tari_hash: FixedHash, +) -> Result { let hashes = create_ordered_transaction_hashes_from_block(&block); let root = tree_hash(&hashes)?; - let coinbase_merkle_proof = create_merkle_proof(&hashes).ok_or_else(|| { + let coinbase_merkle_proof = create_merkle_proof(&hashes, &hashes[0]).ok_or_else(|| { MergeMineError::ValidationError( "create_merkle_proof returned None because the block had no coinbase (which is impossible because the \ Block type does not allow that)" .to_string(), ) })?; + + let t_hash = monero::Hash::from_slice(tari_hash.as_slice()); + let aux_chain_merkle_proof = create_merkle_proof(&ordered_aux_chain_hashes, &t_hash).ok_or_else(|| { + MergeMineError::ValidationError( + "create_merkle_proof returned None, could not find tari hash in ordered aux chain hashes".to_string(), + ) + })?; #[allow(clippy::cast_possible_truncation)] Ok(MoneroPowData { header: block.header, @@ -191,6 +247,7 @@ pub fn construct_monero_data(block: monero::Block, seed: FixedByteArray) -> Resu merkle_root: root, coinbase_merkle_proof, coinbase_tx: block.miner_tx, + aux_chain_merkle_proof, }) } @@ -210,10 +267,15 @@ pub fn create_ordered_transaction_hashes_from_block(block: &monero::Block) -> Ve } /// Inserts merge mining hash into a Monero block -pub fn insert_merge_mining_tag_into_block>( +pub fn insert_merge_mining_tag_and_aux_chain_merkle_root_into_block>( block: &mut monero::Block, hash: T, + aux_chain_count: u8, + aux_nonce: u32, ) -> Result<(), MergeMineError> { + if aux_chain_count == 0 { + return Err(MergeMineError::ZeroAuxChains); + } if hash.as_ref().len() != monero::Hash::len_bytes() { return Err(MergeMineError::HashingError(format!( "Expected source to be {} bytes, but it was {} bytes", @@ -239,9 +301,29 @@ pub fn insert_merge_mining_tag_into_block>( // To circumvent this, we create a new extra field by appending the original extra field to the merge mining field // instead. let hash = monero::Hash::from_slice(hash.as_ref()); - extra_field.0.insert(0, SubField::MergeMining(Some(VarInt(0)), hash)); + let mt_params = MerkleTreeParameters { + number_of_chains: aux_chain_count, + aux_nonce, + }; + let encoded = if aux_chain_count == 1 { + VarInt(0) + } else { + mt_params.to_varint() + }; + extra_field.0.insert(0, SubField::MergeMining(Some(encoded), hash)); block.miner_tx.prefix.extra = extra_field.into(); + + // lets test the block to ensure its serializes correctly + let blocktemplate_blob = serialize_monero_block_to_hex(block)?; + let bytes = hex::decode(blocktemplate_blob).map_err(|_| HexError::HexConversionError {})?; + let de_block = monero::consensus::deserialize::(&bytes[..]) + .map_err(|_| MergeMineError::ValidationError("blocktemplate blob invalid".to_string()))?; + if block != &de_block { + return Err(MergeMineError::SerializeError( + "Blocks dont match after serialization".to_string(), + )); + } Ok(()) } @@ -391,11 +473,13 @@ mod test { validator_node_size: 0, }; let hash = block_header.merge_mining_hash(); - insert_merge_mining_tag_into_block(&mut block, hash).unwrap(); + insert_merge_mining_tag_and_aux_chain_merkle_root_into_block(&mut block, hash, 1, 0).unwrap(); let hashes = create_ordered_transaction_hashes_from_block(&block); assert_eq!(hashes.len(), block.tx_hashes.len() + 1); let root = tree_hash(&hashes).unwrap(); - let coinbase_merkle_proof = create_merkle_proof(&hashes).unwrap(); + let coinbase_merkle_proof = create_merkle_proof(&hashes, &hashes[0]).unwrap(); + let aux_hashes = vec![monero::Hash::from_slice(hash.as_ref())]; + let aux_chain_merkle_proof = create_merkle_proof(&aux_hashes, &aux_hashes[0]).unwrap(); let monero_data = MoneroPowData { header: block.header, @@ -404,6 +488,7 @@ mod test { merkle_root: root, coinbase_merkle_proof, coinbase_tx: block.miner_tx, + aux_chain_merkle_proof, }; let mut serialized = Vec::new(); monero_data.serialize(&mut serialized).unwrap(); @@ -448,7 +533,7 @@ mod test { validator_node_size: 0, }; let hash = block_header.merge_mining_hash(); - insert_merge_mining_tag_into_block(&mut block, hash).unwrap(); + insert_merge_mining_tag_and_aux_chain_merkle_root_into_block(&mut block, hash, 1, 0).unwrap(); let count = 1 + (u16::try_from(block.tx_hashes.len()).unwrap()); let mut hashes = Vec::with_capacity(count as usize); hashes.push(block.miner_tx.hash()); @@ -458,7 +543,9 @@ mod test { } let root = tree_hash(&hashes).unwrap(); assert_eq!(root, hashes[0]); - let coinbase_merkle_proof = create_merkle_proof(&hashes).unwrap(); + let coinbase_merkle_proof = create_merkle_proof(&hashes, &hashes[0]).unwrap(); + let aux_hashes = vec![monero::Hash::from_slice(hash.as_ref())]; + let aux_chain_merkle_proof = create_merkle_proof(&aux_hashes, &aux_hashes[0]).unwrap(); let monero_data = MoneroPowData { header: block.header, randomx_key: FixedByteArray::from_canonical_bytes(&from_hex(&seed_hash).unwrap()).unwrap(), @@ -466,6 +553,7 @@ mod test { merkle_root: root, coinbase_merkle_proof, coinbase_tx: block.miner_tx, + aux_chain_merkle_proof, }; let mut serialized = Vec::new(); monero_data.serialize(&mut serialized).unwrap(); @@ -474,7 +562,7 @@ mod test { pow_data: serialized, }; block_header.pow = pow; - verify_header(&block_header).unwrap(); + verify_header(&block_header, &hash).unwrap(); } #[test] @@ -507,7 +595,9 @@ mod test { hashes.push(item); } let root = tree_hash(&hashes).unwrap(); - let coinbase_merkle_proof = create_merkle_proof(&hashes).unwrap(); + let coinbase_merkle_proof = create_merkle_proof(&hashes, &hashes[0]).unwrap(); + let aux_hashes = vec![monero::Hash::from_slice(block_header.hash().as_ref())]; + let aux_chain_merkle_proof = create_merkle_proof(&aux_hashes, &aux_hashes[0]).unwrap(); let monero_data = MoneroPowData { header: block.header, randomx_key: FixedByteArray::from_canonical_bytes(&from_hex(&seed_hash).unwrap()).unwrap(), @@ -515,6 +605,7 @@ mod test { merkle_root: root, coinbase_merkle_proof, coinbase_tx: block.miner_tx, + aux_chain_merkle_proof, }; let mut serialized = Vec::new(); @@ -524,7 +615,7 @@ mod test { pow_data: serialized, }; block_header.pow = pow; - let err = verify_header(&block_header).unwrap_err(); + let err = verify_header(&block_header, &block_header.hash()).unwrap_err(); unpack_enum!(MergeMineError::ValidationError(details) = err); assert!(details.contains("Expected merge mining tag was not found in Monero coinbase transaction")); } @@ -553,7 +644,7 @@ mod test { validator_node_size: 0, }; let hash = Hash::null(); - insert_merge_mining_tag_into_block(&mut block, hash).unwrap(); + insert_merge_mining_tag_and_aux_chain_merkle_root_into_block(&mut block, hash, 1, 0).unwrap(); let count = 1 + (u16::try_from(block.tx_hashes.len()).unwrap()); let mut hashes = Vec::with_capacity(count as usize); let mut proof = Vec::with_capacity(count as usize); @@ -564,7 +655,9 @@ mod test { proof.push(item); } let root = tree_hash(&hashes).unwrap(); - let coinbase_merkle_proof = create_merkle_proof(&hashes).unwrap(); + let coinbase_merkle_proof = create_merkle_proof(&hashes, &hashes[0]).unwrap(); + let aux_hashes = vec![monero::Hash::from_slice(hash.as_ref())]; + let aux_chain_merkle_proof = create_merkle_proof(&aux_hashes, &aux_hashes[0]).unwrap(); let monero_data = MoneroPowData { header: block.header, randomx_key: FixedByteArray::from_canonical_bytes(&from_hex(&seed_hash).unwrap()).unwrap(), @@ -572,6 +665,7 @@ mod test { merkle_root: root, coinbase_merkle_proof, coinbase_tx: block.miner_tx, + aux_chain_merkle_proof, }; let mut serialized = Vec::new(); monero_data.serialize(&mut serialized).unwrap(); @@ -580,7 +674,7 @@ mod test { pow_data: serialized, }; block_header.pow = pow; - let err = verify_header(&block_header).unwrap_err(); + let err = verify_header(&block_header, &block_header.hash()).unwrap_err(); unpack_enum!(MergeMineError::ValidationError(details) = err); assert!(details.contains("Expected merge mining tag was not found in Monero coinbase transaction")); } @@ -609,15 +703,15 @@ mod test { validator_node_size: 0, }; let hash = block_header.merge_mining_hash(); - insert_merge_mining_tag_into_block(&mut block, hash).unwrap(); + insert_merge_mining_tag_and_aux_chain_merkle_root_into_block(&mut block, hash, 1, 0).unwrap(); #[allow(clippy::redundant_clone)] let mut block_header2 = block_header.clone(); block_header2.version = 1; let hash2 = block_header2.merge_mining_hash(); - assert!(extract_tari_hash_from_block(&block).is_ok()); + assert!(extract_aux_merkle_root_from_block(&block).is_ok()); // Try via the API - this will fail because more than one merge mining tag is not allowed - assert!(insert_merge_mining_tag_into_block(&mut block, hash2).is_err()); + assert!(insert_merge_mining_tag_and_aux_chain_merkle_root_into_block(&mut block, hash2, 1, 0).is_err()); // Now bypass the API - this will effectively allow us to insert more than one merge mining tag, // like trying to sneek it in. Later on, when we call `verify_header(&block_header)`, it should fail. @@ -627,7 +721,7 @@ mod test { block.miner_tx.prefix.extra = extra_field.into(); // Trying to extract the Tari hash will fail because there are more than one merge mining tag - let err = extract_tari_hash_from_block(&block).unwrap_err(); + let err = extract_aux_merkle_root_from_block(&block).unwrap_err(); unpack_enum!(MergeMineError::ValidationError(details) = err); assert!(details.contains("More than one merge mining tag found in coinbase")); @@ -640,7 +734,9 @@ mod test { } let root = tree_hash(&hashes).unwrap(); assert_eq!(root, hashes[0]); - let coinbase_merkle_proof = create_merkle_proof(&hashes).unwrap(); + let coinbase_merkle_proof = create_merkle_proof(&hashes, &hashes[0]).unwrap(); + let aux_hashes = vec![monero::Hash::from_slice(hash.as_ref())]; + let aux_chain_merkle_proof = create_merkle_proof(&aux_hashes, &aux_hashes[0]).unwrap(); let monero_data = MoneroPowData { header: block.header, randomx_key: FixedByteArray::from_canonical_bytes(&from_hex(&seed_hash).unwrap()).unwrap(), @@ -648,6 +744,7 @@ mod test { merkle_root: root, coinbase_merkle_proof, coinbase_tx: block.miner_tx, + aux_chain_merkle_proof, }; let mut serialized = Vec::new(); monero_data.serialize(&mut serialized).unwrap(); @@ -658,7 +755,7 @@ mod test { block_header.pow = pow; // Header verification will fail because there are more than one merge mining tag - let err = verify_header(&block_header).unwrap_err(); + let err = verify_header(&block_header, &block_header.hash()).unwrap_err(); unpack_enum!(MergeMineError::ValidationError(details) = err); assert!(details.contains("More than one merge mining tag found in coinbase")); } @@ -705,7 +802,7 @@ mod test { // Now insert the merge mining tag - this would also clean up the extra field and remove the invalid sub-fields let hash = block_header.merge_mining_hash(); - insert_merge_mining_tag_into_block(&mut block, hash).unwrap(); + insert_merge_mining_tag_and_aux_chain_merkle_root_into_block(&mut block, hash, 1, 0).unwrap(); assert!(ExtraField::try_parse(&block.miner_tx.prefix.extra.clone()).is_ok()); // Verify that the merge mining tag is there @@ -745,7 +842,7 @@ mod test { validator_node_size: 0, }; let hash = block_header.merge_mining_hash(); - insert_merge_mining_tag_into_block(&mut block, hash).unwrap(); + insert_merge_mining_tag_and_aux_chain_merkle_root_into_block(&mut block, hash, 1, 0).unwrap(); let count = 1 + (u16::try_from(block.tx_hashes.len()).unwrap()); let mut hashes = Vec::with_capacity(count as usize); let mut proof = Vec::with_capacity(count as usize); @@ -756,7 +853,9 @@ mod test { proof.push(item); } let root = tree_hash(&hashes).unwrap(); - let coinbase_merkle_proof = create_merkle_proof(&hashes).unwrap(); + let coinbase_merkle_proof = create_merkle_proof(&hashes, &hashes[0]).unwrap(); + let aux_hashes = vec![monero::Hash::from_slice(hash.as_ref())]; + let aux_chain_merkle_proof = create_merkle_proof(&aux_hashes, &aux_hashes[0]).unwrap(); let monero_data = MoneroPowData { header: block.header, randomx_key: FixedByteArray::from_canonical_bytes(&from_hex(&seed_hash).unwrap()).unwrap(), @@ -764,6 +863,7 @@ mod test { merkle_root: root, coinbase_merkle_proof, coinbase_tx: Default::default(), + aux_chain_merkle_proof, }; let mut serialized = Vec::new(); monero_data.serialize(&mut serialized).unwrap(); @@ -772,7 +872,7 @@ mod test { pow_data: serialized, }; block_header.pow = pow; - let err = verify_header(&block_header).unwrap_err(); + let err = verify_header(&block_header, &block_header.hash()).unwrap_err(); unpack_enum!(MergeMineError::ValidationError(details) = err); assert!(details.contains("Expected merge mining tag was not found in Monero coinbase transaction")); } @@ -796,13 +896,15 @@ mod test { validator_node_mr: FixedHash::zero(), validator_node_size: 0, }; + let monero_data = MoneroPowData { header: Default::default(), randomx_key: FixedByteArray::default(), transaction_count: 1, merkle_root: Default::default(), - coinbase_merkle_proof: create_merkle_proof(&[Hash::null()]).unwrap(), + coinbase_merkle_proof: create_merkle_proof(&[Hash::null()], &Hash::null()).unwrap(), coinbase_tx: Default::default(), + aux_chain_merkle_proof: Default::default(), }; let mut serialized = Vec::new(); monero_data.serialize(&mut serialized).unwrap(); @@ -811,7 +913,7 @@ mod test { pow_data: serialized, }; block_header.pow = pow; - let err = verify_header(&block_header).unwrap_err(); + let err = verify_header(&block_header, &block_header.hash()).unwrap_err(); unpack_enum!(MergeMineError::ValidationError(details) = err); assert!(details.contains("Expected merge mining tag was not found in Monero coinbase transaction")); } @@ -840,7 +942,7 @@ mod test { validator_node_size: 0, }; let hash = block_header.merge_mining_hash(); - insert_merge_mining_tag_into_block(&mut block, hash).unwrap(); + insert_merge_mining_tag_and_aux_chain_merkle_root_into_block(&mut block, hash, 1, 0).unwrap(); let count = 1 + (u16::try_from(block.tx_hashes.len()).unwrap()); let mut hashes = Vec::with_capacity(count as usize); let mut proof = Vec::with_capacity(count as usize); @@ -851,7 +953,9 @@ mod test { proof.push(item); } - let coinbase_merkle_proof = create_merkle_proof(&hashes).unwrap(); + let coinbase_merkle_proof = create_merkle_proof(&hashes, &hashes[0]).unwrap(); + let aux_hashes = vec![monero::Hash::from_slice(hash.as_ref())]; + let aux_chain_merkle_proof = create_merkle_proof(&aux_hashes, &aux_hashes[0]).unwrap(); let monero_data = MoneroPowData { header: block.header, randomx_key: FixedByteArray::from_canonical_bytes(&from_hex(&seed_hash).unwrap()).unwrap(), @@ -859,6 +963,7 @@ mod test { merkle_root: Hash::null(), coinbase_merkle_proof, coinbase_tx: block.miner_tx, + aux_chain_merkle_proof, }; let mut serialized = Vec::new(); monero_data.serialize(&mut serialized).unwrap(); @@ -867,7 +972,7 @@ mod test { pow_data: serialized, }; block_header.pow = pow; - let err = verify_header(&block_header).unwrap_err(); + let err = verify_header(&block_header, &block_header.hash()).unwrap_err(); unpack_enum!(MergeMineError::InvalidMerkleRoot = err); } diff --git a/base_layer/core/src/proof_of_work/monero_rx/merkle_tree.rs b/base_layer/core/src/proof_of_work/monero_rx/merkle_tree.rs index 1ea67d86ab..9eaeede7b7 100644 --- a/base_layer/core/src/proof_of_work/monero_rx/merkle_tree.rs +++ b/base_layer/core/src/proof_of_work/monero_rx/merkle_tree.rs @@ -35,7 +35,8 @@ use monero::{ use crate::proof_of_work::monero_rx::error::MergeMineError; -const MAX_MERKLE_TREE_BYTES: usize = 4096; +// Binary tree of depth 32 means u32::MAX tree, this is more than large enough to support most trees. +const MAX_MERKLE_TREE_PROOF_SIZE: usize = 32; /// Returns the Keccak 256 hash of the byte input fn cn_fast_hash(data: &[u8]) -> Hash { @@ -128,6 +129,7 @@ pub fn tree_hash(hashes: &[Hash]) -> Result { #[cfg_attr(test, derive(PartialEq))] pub struct MerkleProof { branch: Vec, + path_bitmap: u32, } impl BorshSerialize for MerkleProof { @@ -136,6 +138,7 @@ impl BorshSerialize for MerkleProof { for hash in &self.branch { hash.consensus_encode(writer)?; } + writer.write_varint(self.path_bitmap)?; Ok(()) } } @@ -144,10 +147,10 @@ impl BorshDeserialize for MerkleProof { fn deserialize_reader(reader: &mut R) -> Result where R: io::Read { let len = reader.read_varint()?; - if len > MAX_MERKLE_TREE_BYTES { + if len >= MAX_MERKLE_TREE_PROOF_SIZE { return Err(io::Error::new( io::ErrorKind::InvalidInput, - "Larger than max merkle tree bytes".to_string(), + "Larger than max merkle tree length".to_string(), )); } let mut branch = Vec::with_capacity(len); @@ -157,13 +160,17 @@ impl BorshDeserialize for MerkleProof { .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err.to_string()))?, ); } - Ok(Self { branch }) + let path_bitmap = reader.read_varint()?; + Ok(Self { branch, path_bitmap }) } } impl MerkleProof { - fn try_construct(branch: Vec) -> Option { - Some(Self { branch }) + fn try_construct(branch: Vec, path_bitmap: u32) -> Option { + if branch.len() >= MAX_MERKLE_TREE_PROOF_SIZE { + return None; + } + Some(Self { branch, path_bitmap }) } /// Returns the merkle proof branch as a list of Monero hashes @@ -172,21 +179,48 @@ impl MerkleProof { &self.branch } - /// Calculates the merkle root hash from the provide Monero hash + /// returns the path bitmap of the proof + pub fn path(&self) -> u32 { + self.path_bitmap + } + /// The coinbase must be the first transaction in the block, so /// that you can't have multiple coinbases in a block. That means the coinbase - /// is always the leftmost branch in the merkle tree + /// is always the leftmost branch in the merkle tree, this test if the given proof is for the left most branch in + /// the merkle tree + pub fn check_coinbase_path(&self) -> bool { + if self.path_bitmap == 0b00000000 { + return true; + } + false + } + + /// Calculates the merkle root hash from the provide Monero hash pub fn calculate_root(&self, hash: &Hash) -> Hash { + self.calculate_root_with_pos(hash).0 + } + + pub fn calculate_root_with_pos(&self, hash: &Hash) -> (Hash, u32) { if self.branch.is_empty() { - return *hash; + return (*hash, 0); } let mut root = *hash; - for hash in &self.branch { - root = cn_fast_hash2(&root, hash); + let depth = self.branch.len(); + let mut pos = 0; + let mut multiplier = 1; + for d in 0..depth { + if (self.path_bitmap >> (depth - d - 1)) & 1 > 0 { + root = cn_fast_hash2(&self.branch[d], &root); + } else { + root = cn_fast_hash2(&root, &self.branch[d]); + pos += multiplier; + } + // this cant overflow as the max depth is 32, and 2^32 == u32::MAX + multiplier *= 2; } - root + (root, pos) } } @@ -194,21 +228,32 @@ impl Default for MerkleProof { fn default() -> Self { Self { branch: vec![Hash::null()], + path_bitmap: 0, } } } /// Creates a merkle proof for the given hash within the set of hashes. This function returns None if the hash is not in -/// hashes. +/// hashes. This is a port of Monero's tree_branch function #[allow(clippy::cognitive_complexity)] -pub fn create_merkle_proof(hashes: &[Hash]) -> Option { - // Monero coinbase rules specify that the coinbase should be hash[0] +pub fn create_merkle_proof(hashes: &[Hash], hash: &Hash) -> Option { match hashes.len() { 0 => None, - 1 => MerkleProof::try_construct(vec![]), - 2 => MerkleProof::try_construct(vec![hashes[1]]), + 1 => { + if hashes[0] != *hash { + return None; + } + MerkleProof::try_construct(vec![], 0) + }, + 2 => hashes.iter().enumerate().find_map(|(pos, h)| { + if h != hash { + return None; + } + let i = usize::from(pos == 0); + MerkleProof::try_construct(vec![hashes[i]], u32::from(pos != 0)) + }), len => { - let mut idx = 0; + let mut idx = hashes.iter().position(|node| node == hash)?; let mut count = tree_hash_count(len).ok()?; let mut ints = vec![Hash::null(); count]; @@ -217,12 +262,14 @@ pub fn create_merkle_proof(hashes: &[Hash]) -> Option { ints[..c].copy_from_slice(&hashes[..c]); let mut branch = Vec::new(); + let mut path = 0u32; let mut i = c; for (j, val) in ints.iter_mut().enumerate().take(count).skip(c) { // Left or right if idx == i || idx == i + 1 { let ii = if idx == i { i + 1 } else { i }; branch.push(hashes[ii]); + path = (path << 1) | u32::from(idx != i); idx = j; } *val = cn_fast_hash2(&hashes[i], &hashes[i + 1]); @@ -238,6 +285,7 @@ pub fn create_merkle_proof(hashes: &[Hash]) -> Option { if idx == i || idx == i + 1 { let ii = if idx == i { i + 1 } else { i }; branch.push(ints[ii]); + path = (path << 1) | u32::from(idx != i); idx = j; } ints[j] = cn_fast_hash2(&ints[i], &ints[i + 1]); @@ -248,9 +296,10 @@ pub fn create_merkle_proof(hashes: &[Hash]) -> Option { if idx == 0 || idx == 1 { let ii = usize::from(idx == 0); branch.push(ints[ii]); + path = (path << 1) | u32::from(idx != 0); } - MerkleProof::try_construct(branch) + MerkleProof::try_construct(branch, path) }, } } @@ -268,7 +317,79 @@ mod test { use super::*; use crate::proof_of_work::randomx_factory::RandomXFactory; + mod quicktest { + use monero::Hash; + use quickcheck::{quickcheck, Arbitrary, Gen}; + + use crate::proof_of_work::monero_rx::merkle_tree::{MerkleProof, MAX_MERKLE_TREE_PROOF_SIZE}; + + #[derive(Clone, Debug)] + struct QuickHash { + pub bits: Vec, + } + + impl Arbitrary for QuickHash { + fn arbitrary(g: &mut Gen) -> QuickHash { + let mut hash = Vec::new(); + for _ in 0..32 { + hash.push(u8::arbitrary(g)); + } + QuickHash { bits: hash } + } + } + + fn create_monero_hashes(input_vec: Vec) -> Vec { + input_vec + .into_iter() + .map(|v| Hash::from_slice(v.bits.as_slice())) + .collect() + } + #[test] + fn test_create() { + fn try_create(input_vec: Vec, path: u32) -> bool { + let hashes = create_monero_hashes(input_vec); + let length = hashes.len(); + let res = MerkleProof::try_construct(hashes, path); + if length >= MAX_MERKLE_TREE_PROOF_SIZE { + return res.is_none(); + } + res.is_some() + } + quickcheck(try_create as fn(Vec, u32) -> bool) + } + + #[test] + fn test_proof() { + fn proof_first(input_vec: Vec, path: u32) -> bool { + if input_vec.is_empty() { + return true; + } + let hashes = create_monero_hashes(input_vec); + let hash = hashes[0]; + let length = hashes.len(); + if length >= MAX_MERKLE_TREE_PROOF_SIZE { + return true; + } + let proof = MerkleProof::try_construct(hashes, path).unwrap(); + proof.calculate_root(&hash); + true + } + fn proof_random(input_vec: Vec, hash: QuickHash, path: u32) -> bool { + let hashes = create_monero_hashes(input_vec); + let hash = Hash::from_slice(hash.bits.as_slice()); + let length = hashes.len(); + if length >= MAX_MERKLE_TREE_PROOF_SIZE { + return true; + } + let proof = MerkleProof::try_construct(hashes, path).unwrap(); + proof.calculate_root(&hash); + true + } + quickcheck(proof_first as fn(Vec, u32) -> bool); + quickcheck(proof_random as fn(Vec, QuickHash, u32) -> bool); + } + } mod tree_hash { use super::*; @@ -445,16 +566,18 @@ mod test { #[test] fn empty_hashset_has_no_proof() { - assert!(create_merkle_proof(&[]).is_none()); + assert!(create_merkle_proof(&[], &Hash::null()).is_none()); } #[test] fn single_hash_is_its_own_proof() { let tx_hashes = &[Hash::from_str("fa58575f7d1d377709f1621fac98c758860ca6dc5f2262be9ce5fd131c370d1a").unwrap()]; - let proof = create_merkle_proof(&tx_hashes[..]).unwrap(); + let proof = create_merkle_proof(&tx_hashes[..], &tx_hashes[0]).unwrap(); assert_eq!(proof.branch.len(), 0); assert_eq!(proof.calculate_root(&tx_hashes[0]), tx_hashes[0]); + + assert!(create_merkle_proof(&tx_hashes[..], &Hash::null()).is_none()); } #[test] @@ -468,35 +591,76 @@ mod test { .collect::>(); let expected_root = cn_fast_hash2(&tx_hashes[0], &tx_hashes[1]); - let proof = create_merkle_proof(tx_hashes).unwrap(); + let proof = create_merkle_proof(tx_hashes, &tx_hashes[0]).unwrap(); + assert_eq!(proof.branch()[0], tx_hashes[1]); + assert_eq!(proof.branch.len(), 1); + assert_eq!(proof.branch[0], tx_hashes[1]); + assert_eq!(proof.path_bitmap, 0b00000000); assert_eq!(proof.calculate_root(&tx_hashes[0]), expected_root); + + let proof = create_merkle_proof(tx_hashes, &tx_hashes[1]).unwrap(); + assert_eq!(proof.branch()[0], tx_hashes[0]); + assert_eq!(proof.calculate_root(&tx_hashes[1]), expected_root); + + assert!(create_merkle_proof(tx_hashes, &Hash::null()).is_none()); } #[test] fn simple_proof_construction() { - // { root } - // / \ - // h0123 h4567 - // / \ / \ - // h01 h23 h45 h67 - // / \ / \ / \ / \ - // h0 h1 h2 h3 h4 h5 h6 h7 - let hashes = (1..=8).map(|i| Hash::from([i; 32])).collect::>(); + // { root } + // / \ + // h01 h2345 + // / \ / \ + // h0 h1 h23 h45 + // / \ / \ + // h2 h3 h4 h5 + + let hashes = (1..=6).map(|i| Hash::from([i - 1; 32])).collect::>(); let h23 = cn_fast_hash2(&hashes[2], &hashes[3]); let h45 = cn_fast_hash2(&hashes[4], &hashes[5]); - let h67 = cn_fast_hash2(&hashes[6], &hashes[7]); let h01 = cn_fast_hash2(&hashes[0], &hashes[1]); - let h0123 = cn_fast_hash2(&h01, &h23); - let h4567 = cn_fast_hash2(&h45, &h67); - let expected_root = cn_fast_hash2(&h0123, &h4567); + let h2345 = cn_fast_hash2(&h23, &h45); + let expected_root = cn_fast_hash2(&h01, &h2345); // Proof for h0 - let proof = create_merkle_proof(&hashes).unwrap(); + let proof = create_merkle_proof(&hashes, &hashes[0]).unwrap(); assert_eq!(proof.calculate_root(&hashes[0]), expected_root); - assert_eq!(proof.branch().len(), 3); + assert_eq!(proof.branch().len(), 2); assert_eq!(proof.branch()[0], hashes[1]); - assert_eq!(proof.branch()[1], h23); - assert_eq!(proof.branch()[2], h4567) + assert_eq!(proof.branch()[1], h2345); + assert_eq!(proof.path_bitmap, 0b00000000); + + // Proof for h2 + let proof = create_merkle_proof(&hashes, &hashes[2]).unwrap(); + assert_eq!(proof.calculate_root(&hashes[2]), expected_root); + assert_eq!(proof.path_bitmap, 0b00000001); + let branch = proof.branch(); + assert_eq!(branch[0], hashes[3]); + assert_eq!(branch[1], h45); + assert_eq!(branch[2], h01); + assert_eq!(branch.len(), 3); + + // Proof for h5 + let proof = create_merkle_proof(&hashes, &hashes[5]).unwrap(); + assert_eq!(proof.calculate_root(&hashes[5]), expected_root); + assert_eq!(proof.branch.len(), 3); + assert_eq!(proof.path_bitmap, 0b00000111); + let branch = proof.branch(); + assert_eq!(branch[0], hashes[4]); + assert_eq!(branch[1], h23); + assert_eq!(branch[2], h01); + assert_eq!(branch.len(), 3); + + // Proof for h4 + let proof = create_merkle_proof(&hashes, &hashes[4]).unwrap(); + assert_eq!(proof.calculate_root(&hashes[4]), expected_root); + assert_eq!(proof.branch.len(), 3); + assert_eq!(proof.path_bitmap, 0b00000011); + let branch = proof.branch(); + assert_eq!(branch[0], hashes[5]); + assert_eq!(branch[1], h23); + assert_eq!(branch[2], h01); + assert_eq!(branch.len(), 3); } #[test] @@ -522,8 +686,10 @@ mod test { let expected_root = tree_hash(tx_hashes).unwrap(); - let hash = Hash::from_str("d96756959949db23764592fea0bfe88c790e1fd131dabb676948b343aa9ecc24").unwrap(); - let proof = create_merkle_proof(tx_hashes).unwrap(); + let hash = Hash::from_str("fa58575f7d1d377709f1621fac98c758860ca6dc5f2262be9ce5fd131c370d1a").unwrap(); + let proof = create_merkle_proof(tx_hashes, &hash).unwrap(); + + assert_eq!(proof.path_bitmap, 0b00001111); assert_eq!(proof.calculate_root(&hash), expected_root); @@ -547,8 +713,11 @@ mod test { let expected_root = tree_hash(&tx_hashes).unwrap(); - let hash = tx_hashes.first().unwrap(); - let proof = create_merkle_proof(&tx_hashes).unwrap(); + let hash = tx_hashes.last().unwrap(); + let proof = create_merkle_proof(&tx_hashes, hash).unwrap(); + + assert_eq!(proof.branch.len(), 16); + assert_eq!(proof.path_bitmap, 0b1111_1111_1111_1111); assert_eq!(proof.calculate_root(hash), expected_root); @@ -560,7 +729,7 @@ mod test { fn test_borsh_de_serialization() { let tx_hashes = &[Hash::from_str("fa58575f7d1d377709f1621fac98c758860ca6dc5f2262be9ce5fd131c370d1a").unwrap()]; - let proof = create_merkle_proof(&tx_hashes[..]).unwrap(); + let proof = create_merkle_proof(&tx_hashes[..], &tx_hashes[0]).unwrap(); let mut buf = Vec::new(); proof.serialize(&mut buf).unwrap(); buf.extend_from_slice(&[1, 2, 3]); diff --git a/base_layer/core/src/proof_of_work/monero_rx/merkle_tree_parameters.rs b/base_layer/core/src/proof_of_work/monero_rx/merkle_tree_parameters.rs new file mode 100644 index 0000000000..fc785315d1 --- /dev/null +++ b/base_layer/core/src/proof_of_work/monero_rx/merkle_tree_parameters.rs @@ -0,0 +1,278 @@ +// Copyright 2023, 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::{cmp::min, convert::TryFrom}; + +use monero::VarInt; + +// This is based on https://github.com/SChernykh/p2pool/blob/merge-mining/docs/MERGE_MINING.MD#merge-mining-tx_extra-tag-format +#[derive(Debug, Clone, PartialEq)] +pub struct MerkleTreeParameters { + pub number_of_chains: u8, + pub aux_nonce: u32, +} + +impl MerkleTreeParameters { + pub fn from_varint(merkle_tree_varint: VarInt) -> MerkleTreeParameters { + let bits = get_decode_bits(merkle_tree_varint.0) + 1; + let number_of_chains = get_aux_chain_count(merkle_tree_varint.0, bits); + let aux_nonce = get_aux_nonce(merkle_tree_varint.0, bits); + MerkleTreeParameters { + number_of_chains, + aux_nonce, + } + } + + pub fn to_varint(&self) -> VarInt { + let size = u8::try_from(self.number_of_chains.leading_zeros()) + .expect("This cant fail, u8 can only have 8 leading 0's which will fit in 255"); + let mut size_bits = encode_bits(7 - size); + let mut n_bits = encode_aux_chain_count(self.number_of_chains, 8 - size); + let mut nonce_bits = encode_aux_nonce(self.aux_nonce); + // this wont underflow as max size will be size_bits(3) + n_bits(8) + nonce_bits(32) = 43 + let mut zero_bits = vec![0; 64 - size_bits.len() - n_bits.len() - nonce_bits.len()]; + size_bits.append(&mut n_bits); + size_bits.append(&mut nonce_bits); + size_bits.append(&mut zero_bits); + let num: u64 = size_bits.iter().fold(0, |result, &bit| (result << 1) ^ u64::from(bit)); + VarInt(num) + } +} + +fn get_decode_bits(num: u64) -> u8 { + let bits_num: Vec = (61..=63).rev().map(|n| ((num >> n) & 1) as u8).collect(); + bits_num.iter().fold(0, |result, &bit| (result << 1) ^ bit) +} + +fn encode_bits(num: u8) -> Vec { + (0..=2).rev().map(|n| (num >> n) & 1).collect() +} + +fn get_aux_chain_count(num: u64, bits: u8) -> u8 { + let start = 60 - min(8, bits) + 1; + let bits_num: Vec = (start..=60).rev().map(|n| ((num >> n) & 1) as u8).collect(); + bits_num.iter().fold(0, |result, &bit| (result << 1) ^ bit) +} + +fn encode_aux_chain_count(num: u8, bit_length: u8) -> Vec { + (0..bit_length).rev().map(|n| (num >> n) & 1).collect() +} + +fn get_aux_nonce(num: u64, bits: u8) -> u32 { + let start = 60 - min(8, u32::from(bits)) + 1 - 32; + let end = 60 - min(8, u32::from(bits)); + let bits_num: Vec = (start..=end).rev().map(|n| ((num >> n) & 1) as u32).collect(); + bits_num.iter().fold(0, |result, &bit| (result << 1) ^ bit) +} + +fn encode_aux_nonce(num: u32) -> Vec { + (0..=31).rev().map(|n| ((num >> n) & 1) as u8).collect() +} + +#[cfg(test)] +mod test { + use crate::proof_of_work::monero_rx::merkle_tree_parameters::{ + encode_aux_chain_count, + encode_aux_nonce, + encode_bits, + get_aux_chain_count, + get_aux_nonce, + get_decode_bits, + }; + + #[test] + fn en_decode_bits_test() { + let num = 0b1100000000000000000000000000000000000000000000000000000000000101; + let bit = get_decode_bits(num); + assert_eq!(bit, 6); + let bits = encode_bits(6); + let array = vec![1, 1, 0]; + assert_eq!(bits, array); + + let num = 0b0100000000000000000000000000000000000000000000000000000000000101; + let bit = get_decode_bits(num); + assert_eq!(bit, 2); + let bits = encode_bits(2); + let array = vec![0, 1, 0]; + assert_eq!(bits, array); + + let num = 0b1110000000000000000000000000000000000000000000000000000000000101; + let bit = get_decode_bits(num); + assert_eq!(bit, 7); + let bits = encode_bits(7); + let array = vec![1, 1, 1]; + assert_eq!(bits, array); + + let num = 0b0011000000000000000000000000000000000000000000000000000000000101; + let bit = get_decode_bits(num); + assert_eq!(bit, 1); + let bits = encode_bits(1); + let array = vec![0, 0, 1]; + assert_eq!(bits, array); + } + + #[test] + fn get_decode_aux_chain_test() { + let num = 0b1101111111100000000000000000000000000000000000000000000000000101; + let aux_number = get_aux_chain_count(num, 8); + assert_eq!(aux_number, 255); + let bits = encode_aux_chain_count(255, 8); + let array = vec![1, 1, 1, 1, 1, 1, 1, 1]; + assert_eq!(bits, array); + + let num = 0b1100000000100000000000000000000000000000000000000000000000000101; + let aux_number = get_aux_chain_count(num, 8); + assert_eq!(aux_number, 1); + let bits = encode_aux_chain_count(1, 8); + let array = vec![0, 0, 0, 0, 0, 0, 0, 1]; + assert_eq!(bits, array); + + let num = 0b1100000000000000000000000000000000000000000000000000000000000101; + let aux_number = get_aux_chain_count(num, 8); + assert_eq!(aux_number, 0); + let bits = encode_aux_chain_count(0, 8); + let array = vec![0, 0, 0, 0, 0, 0, 0, 0]; + assert_eq!(bits, array); + + let num = 0b1100111000000000000000000000000000000000000000000000000000000101; + let aux_number = get_aux_chain_count(num, 8); + assert_eq!(aux_number, 112); + let bits = encode_aux_chain_count(112, 8); + let array = vec![0, 1, 1, 1, 0, 0, 0, 0]; + assert_eq!(bits, array); + + let num = 0b1100000100000000000000000000000000000000000000000000000000000101; + let aux_number = get_aux_chain_count(num, 8); + assert_eq!(aux_number, 8); + let bits = encode_aux_chain_count(8, 8); + let array = vec![0, 0, 0, 0, 1, 0, 0, 0]; + assert_eq!(bits, array); + + let num = 0b1100000001000000000000000000000000000000000000000000000000000101; + let aux_number = get_aux_chain_count(num, 7); + assert_eq!(aux_number, 1); + let bits = encode_aux_chain_count(1, 7); + let array = vec![0, 0, 0, 0, 0, 0, 1]; + assert_eq!(bits, array); + + let num = 0b1100000010000000000000000000000000000000000000000000000000000101; + let aux_number = get_aux_chain_count(num, 6); + assert_eq!(aux_number, 1); + let bits = encode_aux_chain_count(1, 6); + let array = vec![0, 0, 0, 0, 0, 1]; + assert_eq!(bits, array); + + let num = 0b1100000100000000000000000000000000000000000000000000000000000101; + let aux_number = get_aux_chain_count(num, 5); + assert_eq!(aux_number, 1); + let bits = encode_aux_chain_count(1, 5); + let array = vec![0, 0, 0, 0, 1]; + assert_eq!(bits, array); + + let num = 0b1100000110000000000000000000000000000000000000000000000000000101; + let aux_number = get_aux_chain_count(num, 5); + assert_eq!(aux_number, 1); + + let num = 0b1111000110000000000000000000000000000000000000000000000000000101; + let aux_number = get_aux_chain_count(num, 1); + assert_eq!(aux_number, 1); + let bits = encode_aux_chain_count(1, 1); + let array = vec![1]; + assert_eq!(bits, array); + } + + #[test] + fn get_decode_aux_nonce_test() { + let num = 0b1100000000110000000000000000000000000000000000000000000000000101; + let aux_number = get_aux_nonce(num, 8); + assert_eq!(aux_number, 2147483648); + let bits = encode_aux_nonce(2147483648); + let array = vec![ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + assert_eq!(bits, array); + + let num = 0b1100000000011111111111111111111111111111111000000000000000000101; + let aux_number = get_aux_nonce(num, 8); + assert_eq!(aux_number, u32::MAX); + let bits = encode_aux_nonce(u32::MAX); + let array = vec![ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + ]; + assert_eq!(bits, array); + + let num = 0b1100000000111111111111111111111111111111110000000000000000000101; + let aux_number = get_aux_nonce(num, 7); + assert_eq!(aux_number, u32::MAX); + + let num = 0b1100111111111111111111111111111111110000000000000000000000000101; + let aux_number = get_aux_nonce(num, 1); + assert_eq!(aux_number, u32::MAX); + + let num = 0b1100000000100000000000000000000000000000001000000000000000000101; + let aux_number = get_aux_nonce(num, 8); + assert_eq!(aux_number, 1); + let bits = encode_aux_nonce(1); + let array = vec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + ]; + assert_eq!(bits, array); + + let num = 0b1100000000100000000000000000000000000000000000000000000000000101; + let aux_number = get_aux_nonce(num, 8); + assert_eq!(aux_number, 0); + let bits = encode_aux_nonce(0); + let array = vec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + assert_eq!(bits, array); + } + + mod quicktest { + use quickcheck::{quickcheck, Arbitrary, Gen}; + + use crate::proof_of_work::monero_rx::MerkleTreeParameters; + + impl Arbitrary for MerkleTreeParameters { + fn arbitrary(g: &mut Gen) -> MerkleTreeParameters { + let mut mt = MerkleTreeParameters { + number_of_chains: u8::arbitrary(g), + aux_nonce: u32::arbitrary(g), + }; + if mt.number_of_chains == 0 { + mt.number_of_chains = 1; + }; + mt + } + } + + #[test] + fn test_ser_deserialize() { + fn varint_serialization(mt_params: MerkleTreeParameters) -> bool { + let varint = mt_params.to_varint(); + let deserialize = MerkleTreeParameters::from_varint(varint); + mt_params == deserialize + } + quickcheck(varint_serialization as fn(MerkleTreeParameters) -> bool) + } + } +} diff --git a/base_layer/core/src/proof_of_work/monero_rx/mod.rs b/base_layer/core/src/proof_of_work/monero_rx/mod.rs index bc018fb1d8..7d29264add 100644 --- a/base_layer/core/src/proof_of_work/monero_rx/mod.rs +++ b/base_layer/core/src/proof_of_work/monero_rx/mod.rs @@ -28,8 +28,8 @@ pub use helpers::{ create_blockhashing_blob_from_block, create_ordered_transaction_hashes_from_block, deserialize_monero_block_from_hex, - extract_tari_hash_from_block, - insert_merge_mining_tag_into_block, + extract_aux_merkle_root_from_block, + insert_merge_mining_tag_and_aux_chain_merkle_root_into_block, randomx_difficulty, serialize_monero_block_to_hex, verify_header, @@ -42,7 +42,9 @@ mod pow_data; pub use pow_data::MoneroPowData; mod merkle_tree; +mod merkle_tree_parameters; pub use merkle_tree::{create_merkle_proof, tree_hash}; +pub use merkle_tree_parameters::MerkleTreeParameters; // Re-exports pub use monero::{ consensus::{deserialize, serialize}, diff --git a/base_layer/core/src/proof_of_work/monero_rx/pow_data.rs b/base_layer/core/src/proof_of_work/monero_rx/pow_data.rs index 35b0473c6a..d339d40aaa 100644 --- a/base_layer/core/src/proof_of_work/monero_rx/pow_data.rs +++ b/base_layer/core/src/proof_of_work/monero_rx/pow_data.rs @@ -54,6 +54,8 @@ pub struct MoneroPowData { pub coinbase_merkle_proof: MerkleProof, /// Coinbase tx from Monero pub coinbase_tx: monero::Transaction, + /// aux chain merkle proof hashes + pub aux_chain_merkle_proof: MerkleProof, } impl BorshSerialize for MoneroPowData { @@ -64,6 +66,7 @@ impl BorshSerialize for MoneroPowData { self.merkle_root.consensus_encode(writer)?; BorshSerialize::serialize(&self.coinbase_merkle_proof, writer)?; self.coinbase_tx.consensus_encode(writer)?; + BorshSerialize::serialize(&self.aux_chain_merkle_proof, writer)?; Ok(()) } } @@ -80,6 +83,7 @@ impl BorshDeserialize for MoneroPowData { let coinbase_merkle_proof = BorshDeserialize::deserialize_reader(reader)?; let coinbase_tx = monero::Transaction::consensus_decode(reader) .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))?; + let aux_chain_merkle_proof = BorshDeserialize::deserialize_reader(reader)?; Ok(Self { header, randomx_key, @@ -87,6 +91,7 @@ impl BorshDeserialize for MoneroPowData { merkle_root, coinbase_merkle_proof, coinbase_tx, + aux_chain_merkle_proof, }) } } @@ -122,10 +127,10 @@ impl MoneroPowData { } /// Returns true if the coinbase merkle proof produces the `merkle_root` hash, otherwise false - pub fn is_valid_merkle_root(&self) -> bool { + pub fn is_coinbase_valid_merkle_root(&self) -> bool { let coinbase_hash = self.coinbase_tx.hash(); let merkle_root = self.coinbase_merkle_proof.calculate_root(&coinbase_hash); - self.merkle_root == merkle_root + (self.merkle_root == merkle_root) && self.coinbase_merkle_proof.check_coinbase_path() } /// Returns the blockhashing_blob for the Monero block @@ -173,6 +178,7 @@ mod test { merkle_root: Hash::new([10; 32]), coinbase_merkle_proof: MerkleProof::default(), coinbase_tx: Transaction::default(), + aux_chain_merkle_proof: MerkleProof::default(), }; let mut buf = Vec::new(); monero_pow_data.serialize(&mut buf).unwrap(); diff --git a/base_layer/core/src/validation/difficulty_calculator.rs b/base_layer/core/src/validation/difficulty_calculator.rs index fc2f297311..6bdf7435a7 100644 --- a/base_layer/core/src/validation/difficulty_calculator.rs +++ b/base_layer/core/src/validation/difficulty_calculator.rs @@ -51,7 +51,8 @@ impl DifficultyCalculator { constants.min_pow_difficulty(block_header.pow.pow_algo), constants.max_pow_difficulty(block_header.pow.pow_algo), ); - let achieved_target = check_target_difficulty(block_header, target, &self.randomx_factory)?; + let gen_hash = *self.rules.get_genesis_block().hash(); + let achieved_target = check_target_difficulty(block_header, target, &self.randomx_factory, &gen_hash)?; Ok(achieved_target) } diff --git a/base_layer/core/src/validation/header/header_full_validator.rs b/base_layer/core/src/validation/header/header_full_validator.rs index 46a8262ee1..3f99583b15 100644 --- a/base_layer/core/src/validation/header/header_full_validator.rs +++ b/base_layer/core/src/validation/header/header_full_validator.rs @@ -77,9 +77,10 @@ impl HeaderChainLinkedValidator for HeaderFullValidator check_timestamp_ftl(header, &self.rules)?; check_pow_data(header, &self.rules, db)?; + let gen_hash = *self.rules.get_genesis_block().hash(); let achieved_target = if let Some(target) = target_difficulty { - check_target_difficulty(header, target, &self.difficulty_calculator.randomx_factory)? + check_target_difficulty(header, target, &self.difficulty_calculator.randomx_factory, &gen_hash)? } else { self.difficulty_calculator .check_achieved_and_target_difficulty(db, header)? diff --git a/base_layer/core/src/validation/helpers.rs b/base_layer/core/src/validation/helpers.rs index c0f371837b..2df8a469a8 100644 --- a/base_layer/core/src/validation/helpers.rs +++ b/base_layer/core/src/validation/helpers.rs @@ -23,6 +23,7 @@ use std::convert::TryFrom; use log::*; +use tari_common_types::types::FixedHash; use tari_crypto::tari_utilities::{epoch_time::EpochTime, hex::Hex}; use tari_script::TariScript; @@ -119,9 +120,10 @@ pub fn check_target_difficulty( block_header: &BlockHeader, target: Difficulty, randomx_factory: &RandomXFactory, + gen_hash: &FixedHash, ) -> Result { let achieved = match block_header.pow_algo() { - PowAlgorithm::RandomX => randomx_difficulty(block_header, randomx_factory)?, + PowAlgorithm::RandomX => randomx_difficulty(block_header, randomx_factory, gen_hash)?, PowAlgorithm::Sha3x => sha3x_difficulty(block_header)?, }; diff --git a/base_layer/core/tests/tests/block_validation.rs b/base_layer/core/tests/tests/block_validation.rs index d7542cac2b..013512c73b 100644 --- a/base_layer/core/tests/tests/block_validation.rs +++ b/base_layer/core/tests/tests/block_validation.rs @@ -118,6 +118,7 @@ async fn test_monero_blocks() { .add_consensus_constants(cc) .build() .unwrap(); + let gen_hash = *cm.get_genesis_block().hash(); let difficulty_calculator = DifficultyCalculator::new(cm.clone(), RandomXFactory::default()); let header_validator = HeaderFullValidator::new(cm.clone(), difficulty_calculator); let db = create_store_with_consensus_and_validators( @@ -175,7 +176,7 @@ async fn test_monero_blocks() { block_3.header.nonce = 1; let hash2 = block_3.hash(); assert_ne!(hash1, hash2); - assert!(verify_header(&block_3.header).is_ok()); + assert!(verify_header(&block_3.header, &gen_hash).is_ok()); match db.add_block(Arc::new(block_3.clone())) { Err(ChainStorageError::ValidationError { source: ValidationError::BlockHeaderError(BlockHeaderValidationError::InvalidNonce), @@ -198,11 +199,13 @@ fn add_monero_data(tblock: &mut Block, seed_key: &str) { .to_string(); let bytes = hex::decode(blocktemplate_blob).unwrap(); let mut mblock = monero_rx::deserialize::(&bytes[..]).unwrap(); - let hash = tblock.header.merge_mining_hash(); - monero_rx::insert_merge_mining_tag_into_block(&mut mblock, hash).unwrap(); + let hash = monero::Hash::from_slice(tblock.header.merge_mining_hash().as_slice()); + monero_rx::insert_merge_mining_tag_and_aux_chain_merkle_root_into_block(&mut mblock, hash, 1, 0).unwrap(); let hashes = monero_rx::create_ordered_transaction_hashes_from_block(&mblock); let merkle_root = monero_rx::tree_hash(&hashes).unwrap(); - let coinbase_merkle_proof = monero_rx::create_merkle_proof(&hashes).unwrap(); + let coinbase_merkle_proof = monero_rx::create_merkle_proof(&hashes, &hashes[0]).unwrap(); + let aux_hashes = vec![hash]; + let aux_chain_merkle_proof = monero_rx::create_merkle_proof(&aux_hashes, &aux_hashes[0]).unwrap(); #[allow(clippy::cast_possible_truncation)] let monero_data = MoneroPowData { header: mblock.header, @@ -211,6 +214,7 @@ fn add_monero_data(tblock: &mut Block, seed_key: &str) { merkle_root, coinbase_merkle_proof, coinbase_tx: mblock.miner_tx, + aux_chain_merkle_proof, }; let mut serialized = Vec::new(); BorshSerialize::serialize(&monero_data, &mut serialized).unwrap();