From 0d779ce6c84c00513d55ed2fdd9e72c153ae5da5 Mon Sep 17 00:00:00 2001 From: Hansie Odendaal Date: Fri, 4 Oct 2024 13:22:20 +0200 Subject: [PATCH] Add input mr into genesis block Added the input merkle root calculation into the genesis block; previously this was just a default hash, which is incorrect. This will enable adding immediate spending of pre-mine outputs in the genesis block. --- .../src/automation/commands.rs | 3 + .../sync/horizon_state_sync/error.rs | 6 +- .../sync/horizon_state_sync/synchronizer.rs | 3 +- base_layer/core/src/blocks/genesis_block.rs | 139 ++++++++++++++---- .../src/chain_storage/blockchain_database.rs | 47 +++++- base_layer/core/src/chain_storage/error.rs | 6 +- .../core/src/chain_storage/lmdb_db/lmdb_db.rs | 3 +- base_layer/core/src/lib.rs | 33 +++++ base_layer/core/src/validation/test.rs | 3 +- .../core/tests/helpers/block_builders.rs | 21 ++- base_layer/tari_mining_helper_ffi/src/lib.rs | 4 +- 11 files changed, 226 insertions(+), 42 deletions(-) diff --git a/applications/minotari_console_wallet/src/automation/commands.rs b/applications/minotari_console_wallet/src/automation/commands.rs index 9a139d4d1a..864c59500c 100644 --- a/applications/minotari_console_wallet/src/automation/commands.rs +++ b/applications/minotari_console_wallet/src/automation/commands.rs @@ -1705,6 +1705,7 @@ pub async fn command_runner( }; let mut error = false; + inputs.sort(); for input in inputs { let input_s = match serde_json::to_string(&input) { Ok(val) => val, @@ -1723,6 +1724,7 @@ pub async fn command_runner( if error { break; } + outputs.sort(); for output in outputs { let utxo_s = match serde_json::to_string(&output) { Ok(val) => val, @@ -1741,6 +1743,7 @@ pub async fn command_runner( if error { break; } + kernels.sort(); for kernel in kernels { let kernel_s = match serde_json::to_string(&kernel) { Ok(val) => val, diff --git a/base_layer/core/src/base_node/sync/horizon_state_sync/error.rs b/base_layer/core/src/base_node/sync/horizon_state_sync/error.rs index 6aff7e4510..be3918a792 100644 --- a/base_layer/core/src/base_node/sync/horizon_state_sync/error.rs +++ b/base_layer/core/src/base_node/sync/horizon_state_sync/error.rs @@ -39,6 +39,7 @@ use crate::{ common::{BanPeriod, BanReason}, transactions::transaction_components::TransactionError, validation::ValidationError, + MrHashError, }; #[derive(Debug, Error)] @@ -100,6 +101,8 @@ pub enum HorizonSyncError { SMTError(#[from] SMTError), #[error("ByteArrayError error: {0}")] ByteArrayError(String), + #[error("FixedHash size error: {0}")] + MrHashError(#[from] MrHashError), } impl From for HorizonSyncError { @@ -131,7 +134,8 @@ impl HorizonSyncError { HorizonSyncError::ConnectivityError(_) | HorizonSyncError::NoMoreSyncPeers(_) | HorizonSyncError::PeerNotFound | - HorizonSyncError::JoinError(_) => None, + HorizonSyncError::JoinError(_) | + HorizonSyncError::MrHashError(_) => None, // short ban err @ HorizonSyncError::MaxLatencyExceeded { .. } | diff --git a/base_layer/core/src/base_node/sync/horizon_state_sync/synchronizer.rs b/base_layer/core/src/base_node/sync/horizon_state_sync/synchronizer.rs index 6c40bd7756..ded89d17d7 100644 --- a/base_layer/core/src/base_node/sync/horizon_state_sync/synchronizer.rs +++ b/base_layer/core/src/base_node/sync/horizon_state_sync/synchronizer.rs @@ -51,6 +51,7 @@ use crate::{ chain_storage::{async_db::AsyncBlockchainDb, BlockchainBackend, ChainStorageError, MmrTree}, common::{rolling_avg::RollingAverageTime, BanPeriod}, consensus::ConsensusManager, + output_mr_hash_from_smt, proto::base_node::{sync_utxos_response::Txo, SyncKernelsRequest, SyncUtxosRequest, SyncUtxosResponse}, transactions::transaction_components::{ transaction_output::batch_verify_range_proofs, @@ -783,7 +784,7 @@ impl<'a, B: BlockchainBackend + 'static> HorizonStateSynchronization<'a, B> { // Helper function to check the output SMT root hash against the expected root hash. fn check_output_smt_root_hash(output_smt: &mut OutputSmt, header: &BlockHeader) -> Result<(), HorizonSyncError> { - let root = FixedHash::try_from(output_smt.hash().as_slice())?; + let root = output_mr_hash_from_smt(output_smt)?; if root != header.output_mr { warn!( target: LOG_TARGET, diff --git a/base_layer/core/src/blocks/genesis_block.rs b/base_layer/core/src/blocks/genesis_block.rs index ef24496e79..95822c387e 100644 --- a/base_layer/core/src/blocks/genesis_block.rs +++ b/base_layer/core/src/blocks/genesis_block.rs @@ -26,17 +26,24 @@ use chrono::{DateTime, FixedOffset}; use tari_common::configuration::Network; use tari_common_types::types::{FixedHash, PrivateKey}; use tari_crypto::tari_utilities::hex::*; -use tari_mmr::sparse_merkle_tree::{NodeKey, ValueHash}; +use tari_mmr::{ + pruned_hashset::PrunedHashSet, + sparse_merkle_tree::{NodeKey, ValueHash}, +}; use tari_utilities::ByteArray; use crate::{ blocks::{block::Block, BlockHeader, BlockHeaderAccumulatedData, ChainBlock}, + input_mr_hash_from_pruned_mmr, + kernel_mr_hash_from_mmr, + output_mr_hash_from_smt, proof_of_work::{AccumulatedDifficulty, Difficulty, PowAlgorithm, PowData, ProofOfWork}, transactions::{ aggregated_body::AggregateBody, transaction_components::{TransactionInput, TransactionKernel, TransactionOutput}, }, OutputSmt, + PrunedInputMmr, }; /// Returns the genesis block for the selected network. @@ -53,11 +60,11 @@ pub fn get_genesis_block(network: Network) -> ChainBlock { } fn add_pre_mine_utxos_to_genesis_block(file: &str, block: &mut Block) { - let mut utxos = Vec::new(); + let mut outputs = Vec::new(); let mut inputs = Vec::new(); for line in file.lines() { - if let Ok(utxo) = serde_json::from_str::(line) { - utxos.push(utxo); + if let Ok(output) = serde_json::from_str::(line) { + outputs.push(output); } else if let Ok(input) = serde_json::from_str::(line) { inputs.push(input); } else if let Ok(kernel) = serde_json::from_str::(line) { @@ -67,8 +74,9 @@ fn add_pre_mine_utxos_to_genesis_block(file: &str, block: &mut Block) { panic!("Error: Could not deserialize line: {} in file: {}", line, file); } } - block.header.output_smt_size += utxos.len() as u64; - block.body.add_outputs(utxos); + block.header.output_smt_size += outputs.len() as u64; + block.header.output_smt_size -= inputs.len() as u64; + block.body.add_outputs(outputs); block.body.add_inputs(inputs); block.body.sort(); } @@ -94,13 +102,24 @@ fn print_mr_values(block: &mut Block, print: bool) { let smt_node = ValueHash::try_from(o.smt_hash(block.header.height).as_slice()).unwrap(); output_smt.insert(smt_key, smt_node).unwrap(); } + for i in block.body.inputs() { + let smt_key = NodeKey::try_from(i.commitment().unwrap().as_bytes()).unwrap(); + output_smt.delete(&smt_key).unwrap(); + } let vn_mmr = calculate_validator_node_mr(&[]); - block.header.kernel_mr = FixedHash::try_from(kernel_mmr.get_merkle_root().unwrap()).unwrap(); - block.header.output_mr = FixedHash::try_from(output_smt.hash().as_slice()).unwrap(); + let mut input_mmr = PrunedInputMmr::new(PrunedHashSet::default()); + for input in block.body.inputs() { + input_mmr.push(input.canonical_hash().to_vec()).unwrap(); + } + + block.header.kernel_mr = kernel_mr_hash_from_mmr(&kernel_mmr).unwrap(); + block.header.output_mr = output_mr_hash_from_smt(&mut output_smt).unwrap(); + block.header.input_mr = input_mr_hash_from_pruned_mmr(&input_mmr).unwrap(); block.header.validator_node_mr = FixedHash::try_from(vn_mmr).unwrap(); println!(); println!("kernel mr: {}", block.header.kernel_mr.to_hex()); + println!("input mr: {}", block.header.input_mr.to_hex()); println!("output mr: {}", block.header.output_mr.to_hex()); println!("vn mr: {}", block.header.validator_node_mr.to_hex()); } @@ -122,6 +141,8 @@ pub fn get_stagenet_genesis_block() -> ChainBlock { // Hardcode the Merkle roots once they've been computed above block.header.kernel_mr = FixedHash::from_hex("a08ff15219beea81d4131465290443fb3bd99d28b8af85975dbb2c77cb4cb5a0").unwrap(); + block.header.input_mr = + FixedHash::from_hex("212ce6f5f7fc67dcb73b2a8a7a11404703aca210a7c75de9e50d914c9f9942c2").unwrap(); block.header.output_mr = FixedHash::from_hex("435f13e21be06b0d0ae9ad3869ac7c723edd933983fa2e26df843c82594b3245").unwrap(); block.header.validator_node_mr = @@ -166,6 +187,10 @@ fn get_stagenet_genesis_block_raw() -> Block { pub fn get_nextnet_genesis_block() -> ChainBlock { let mut block = get_nextnet_genesis_block_raw(); + // TODO: Fix this hack with the next nextnet reset!! + block.header.input_mr = + FixedHash::from_hex("0000000000000000000000000000000000000000000000000000000000000000").unwrap(); + // Add pre-mine utxos - enable/disable as required let add_pre_mine_utxos = false; if add_pre_mine_utxos { @@ -180,6 +205,8 @@ pub fn get_nextnet_genesis_block() -> ChainBlock { // Hardcode the Merkle roots once they've been computed above block.header.kernel_mr = FixedHash::from_hex("36881d87e25183f5189d2dca5f7da450c399e7006dafd9bd9240f73a5fb3f0ad").unwrap(); + block.header.input_mr = + FixedHash::from_hex("212ce6f5f7fc67dcb73b2a8a7a11404703aca210a7c75de9e50d914c9f9942c2").unwrap(); block.header.output_mr = FixedHash::from_hex("7b65d5140485b44e33eef3690d46c41e4dc5c4520ad7464d7740f376f4f0a728").unwrap(); block.header.validator_node_mr = @@ -238,6 +265,8 @@ pub fn get_mainnet_genesis_block() -> ChainBlock { // Hardcode the Merkle roots once they've been computed above block.header.kernel_mr = FixedHash::from_hex("c4bceeddf911e29f651fe00ae198d4dcdf3b8d27fab7754400e3b66d18d9be95").unwrap(); + block.header.input_mr = + FixedHash::from_hex("212ce6f5f7fc67dcb73b2a8a7a11404703aca210a7c75de9e50d914c9f9942c2").unwrap(); block.header.output_mr = FixedHash::from_hex("084348f0081f9086cb88bc51063bba54bbf76541d56451327393614d89045249").unwrap(); block.header.validator_node_mr = @@ -293,6 +322,8 @@ pub fn get_igor_genesis_block() -> ChainBlock { // Hardcode the Merkle roots once they've been computed above block.header.kernel_mr = FixedHash::from_hex("bc5d677b0b8349adc9d7e4a18ace7406986fc7017866f4fd351ecb0f35d6da5e").unwrap(); + block.header.input_mr = + FixedHash::from_hex("0000000000000000000000000000000000000000000000000000000000000000").unwrap(); block.header.output_mr = FixedHash::from_hex("d227ba7b215eab4dae9e0d5a678b84ffbed1d7d3cebdeafae4704e504bd2e5f3").unwrap(); block.header.validator_node_mr = @@ -353,6 +384,8 @@ pub fn get_esmeralda_genesis_block() -> ChainBlock { // Hardcode the Merkle roots once they've been computed above block.header.kernel_mr = FixedHash::from_hex("351cc183f692dcba280ec4e8988538fc51ffdeeff13ed3ea868026c81df5cc17").unwrap(); + block.header.input_mr = + FixedHash::from_hex("212ce6f5f7fc67dcb73b2a8a7a11404703aca210a7c75de9e50d914c9f9942c2").unwrap(); block.header.output_mr = FixedHash::from_hex("024b4cde6fdc73edbfde822c1496d7bdf156bc25caaf45eb6642fa62ff846964").unwrap(); block.header.validator_node_mr = @@ -448,14 +481,14 @@ fn get_raw_block(genesis_timestamp: &DateTime, not_before_proof: &P height: 0, prev_hash: FixedHash::zero(), timestamp: timestamp.into(), - output_mr: FixedHash::zero(), + output_mr: FixedHash::from_hex("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), output_smt_size: 0, kernel_mr: FixedHash::from_hex("c14803066909d6d22abf0d2d2782e8936afc3f713f2af3a4ef5c42e8400c1303").unwrap(), kernel_mmr_size: 0, validator_node_mr: FixedHash::from_hex("277da65c40b2cf99db86baedb903a3f0a38540f3a94d40c826eecac7e27d5dfc") .unwrap(), validator_node_size: 0, - input_mr: FixedHash::zero(), + input_mr: FixedHash::from_hex("212ce6f5f7fc67dcb73b2a8a7a11404703aca210a7c75de9e50d914c9f9942c2").unwrap(), total_kernel_offset: PrivateKey::from_hex( "0000000000000000000000000000000000000000000000000000000000000000", ) @@ -506,7 +539,7 @@ mod test { // Note: Generate new data for `pub fn get_esmeralda_genesis_block()` and `fn get_esmeralda_genesis_block_raw()` // if consensus values change, e.g. new pre_mine or other let block = get_esmeralda_genesis_block(); - check_block(network, &block, 164, 1); + check_block(network, &block, 0, 164, 1); remove_network_env_var(); } @@ -521,7 +554,7 @@ mod test { // Note: Generate new data for `pub fn get_nextnet_genesis_block()` and `fn get_stagenet_genesis_block_raw()` // if consensus values change, e.g. new pre_mine or other let block = get_nextnet_genesis_block(); - check_block(network, &block, 0, 0); + check_block(network, &block, 0, 0, 0); remove_network_env_var(); } @@ -536,7 +569,7 @@ mod test { // Note: Generate new data for `pub fn get_nextnet_genesis_block()` and `fn get_stagenet_genesis_block_raw()` // if consensus values change, e.g. new pre_mine or other let block = get_mainnet_genesis_block(); - check_block(network, &block, 168, 1); + check_block(network, &block, 0, 168, 1); remove_network_env_var(); } @@ -551,7 +584,7 @@ mod test { // Note: Generate new data for `pub fn get_stagenet_genesis_block()` and `fn get_stagenet_genesis_block_raw()` // if consensus values change, e.g. new pre_mine or other let block = get_stagenet_genesis_block(); - check_block(network, &block, 0, 0); + check_block(network, &block, 0, 0, 0); remove_network_env_var(); } @@ -565,7 +598,7 @@ mod test { } // Note: If outputs and kernels are added, this test will fail unless you explicitly check that network == Igor let block = get_igor_genesis_block(); - check_block(network, &block, 0, 0); + check_block(network, &block, 0, 0, 0); remove_network_env_var(); } @@ -579,14 +612,21 @@ mod test { } // Note: If outputs and kernels are added, this test will fail unless you explicitly check that network == Igor let block = get_localnet_genesis_block(); - check_block(network, &block, 0, 0); + check_block(network, &block, 0, 0, 0); remove_network_env_var(); } - fn check_block(network: Network, block: &ChainBlock, expected_outputs: usize, expected_kernels: usize) { - assert!(block.block().body.inputs().is_empty()); + #[allow(clippy::too_many_lines)] + fn check_block( + network: Network, + block: &ChainBlock, + expected_inputs: usize, + expected_outputs: usize, + expected_kernels: usize, + ) { assert_eq!(block.block().body.kernels().len(), expected_kernels); assert_eq!(block.block().body.outputs().len(), expected_outputs); + assert_eq!(block.block().body.inputs().len(), expected_inputs); let factories = CryptoFactories::default(); let some_output_is_coinbase = block.block().body.outputs().iter().any(|o| o.is_coinbase()); @@ -599,7 +639,7 @@ mod test { block.header().kernel_mmr_size ); assert_eq!( - block.block().body.outputs().len() as u64, + block.block().body.outputs().len() as u64 - block.block().body.inputs().len() as u64, block.header().output_smt_size ); @@ -640,13 +680,62 @@ mod test { )); } } + for i in block.block().body.inputs() { + let smt_key = NodeKey::try_from(i.commitment().unwrap().as_bytes()).unwrap(); + output_smt.delete(&smt_key).unwrap(); + if matches!(i.features().unwrap().output_type, OutputType::ValidatorNodeRegistration) { + let reg = i + .features() + .unwrap() + .sidechain_feature + .as_ref() + .and_then(|f| f.validator_node_registration()) + .unwrap(); + let pos = vn_nodes + .iter() + .position(|v| { + v == &( + reg.public_key().clone(), + reg.derive_shard_key(None, VnEpoch(0), VnEpoch(0), block.hash()), + ) + }) + .unwrap(); + vn_nodes.remove(pos); + } + } + + let mut input_mmr = PrunedInputMmr::new(PrunedHashSet::default()); + for input in block.block().body.inputs() { + input_mmr.push(input.canonical_hash().to_vec()).unwrap(); + } - assert_eq!(kernel_mmr.get_merkle_root().unwrap(), block.header().kernel_mr,); assert_eq!( - FixedHash::try_from(output_smt.hash().as_slice()).unwrap(), - block.header().output_mr, + kernel_mr_hash_from_mmr(&kernel_mmr).unwrap().to_vec().to_hex(), + block.header().kernel_mr.to_vec().to_hex() + ); + assert_eq!( + output_mr_hash_from_smt(&mut output_smt).unwrap().to_vec().to_hex(), + block.header().output_mr.to_vec().to_hex(), + ); + if network == Network::NextNet { + // TODO: Fix this hack with the next nextnet reset!! + assert_eq!( + FixedHash::from_hex("0000000000000000000000000000000000000000000000000000000000000000") + .unwrap() + .to_vec() + .to_hex(), + block.header().input_mr.to_vec().to_hex(), + ); + } else { + assert_eq!( + input_mr_hash_from_pruned_mmr(&input_mmr).unwrap().to_vec().to_hex(), + block.header().input_mr.to_vec().to_hex(), + ); + } + assert_eq!( + calculate_validator_node_mr(&vn_nodes).to_vec().to_hex(), + block.header().validator_node_mr.to_vec().to_hex() ); - assert_eq!(calculate_validator_node_mr(&vn_nodes), block.header().validator_node_mr,); // Check that the pre_mine UTXOs balance (the pre_mine_value consensus constant is set correctly and pre_mine // kernel is correct) @@ -658,14 +747,14 @@ mod test { .iter() .map(|o| o.commitment().unwrap()) .sum::(); - let utxo_sum = block + let output_sum = block .block() .body .outputs() .iter() .map(|o| &o.commitment) .sum::(); - let total_utxo_sum = &utxo_sum - &input_sum; + let total_utxo_sum = &output_sum - &input_sum; let kernel_sum = block.block().body.kernels().iter().map(|k| &k.excess).sum(); let db = create_new_blockchain_with_network(network); diff --git a/base_layer/core/src/chain_storage/blockchain_database.rs b/base_layer/core/src/chain_storage/blockchain_database.rs index b8308e2a8f..dea177ea2d 100644 --- a/base_layer/core/src/chain_storage/blockchain_database.rs +++ b/base_layer/core/src/chain_storage/blockchain_database.rs @@ -89,6 +89,9 @@ use crate::{ ConsensusManager, DomainSeparatedConsensusHasher, }, + input_mr_hash_from_pruned_mmr, + kernel_mr_hash_from_pruned_mmr, + output_mr_hash_from_smt, proof_of_work::{monero_rx::MoneroPowData, PowAlgorithm, TargetDifficultyWindow}, transactions::transaction_components::{TransactionInput, TransactionKernel, TransactionOutput}, validation::{ @@ -247,16 +250,50 @@ where B: BlockchainBackend txn = DbTransaction::new(); blockchain_db.insert_block(genesis_block.clone())?; let body = &genesis_block.block().body; - let utxo_sum = body.outputs().iter().map(|k| &k.commitment).sum::(); + let input_sum = body + .inputs() + .iter() + .map(|k| k.commitment()) + .collect::, _>>()? + .into_iter() + .sum::(); + let output_sum = body.outputs().iter().map(|k| &k.commitment).sum::(); + let total_utxo_sum = &output_sum - &input_sum; let kernel_sum = body.kernels().iter().map(|k| &k.excess).sum::(); txn.update_block_accumulated_data(*genesis_block.hash(), UpdateBlockAccumulatedData { kernel_sum: Some(kernel_sum.clone()), ..Default::default() }); txn.set_pruned_height(0); - txn.set_horizon_data(kernel_sum, utxo_sum); + txn.set_horizon_data(kernel_sum, total_utxo_sum); blockchain_db.write(txn)?; blockchain_db.store_pruning_horizon(config.pruning_horizon)?; + // Sanity check + match blockchain_db.fetch_block_by_hash(*genesis_block.hash(), false)? { + Some(block) => { + let header_from_db = block.block().clone().header; + let header_from_code = genesis_block.block().clone().header; + if header_from_db != header_from_code { + return Err(ChainStorageError::CriticalError( + "Genesis block header in db does not match genesis block in code".into(), + )); + } + let mut body_from_db = block.block().clone().body; + body_from_db.sort(); + let mut body_from_code = genesis_block.block().clone().body; + body_from_code.sort(); + if body_from_db != body_from_code { + return Err(ChainStorageError::CriticalError( + "Genesis block body in db does not match genesis block in code".into(), + )); + } + }, + None => { + return Err(ChainStorageError::CriticalError( + "Genesis block could not be created".into(), + )) + }, + } } else if !blockchain_db.chain_block_or_orphan_block_exists(genesis_block.accumulated_data().hash)? { // Check the genesis block in the DB. error!( @@ -1416,10 +1453,10 @@ pub fn calculate_mmr_roots( }; let mmr_roots = MmrRoots { - kernel_mr: FixedHash::try_from(kernel_mmr.get_merkle_root()?)?, + kernel_mr: kernel_mr_hash_from_pruned_mmr(&kernel_mmr)?, kernel_mmr_size: kernel_mmr.get_leaf_count()? as u64, - input_mr: FixedHash::try_from(input_mmr.get_merkle_root()?)?, - output_mr: FixedHash::try_from(output_smt.hash().as_slice())?, + input_mr: input_mr_hash_from_pruned_mmr(&input_mmr)?, + output_mr: output_mr_hash_from_smt(output_smt)?, output_smt_size: output_smt.size(), validator_node_mr, validator_node_size: validator_node_size as u64, diff --git a/base_layer/core/src/chain_storage/error.rs b/base_layer/core/src/chain_storage/error.rs index 7261030149..2c12af362b 100644 --- a/base_layer/core/src/chain_storage/error.rs +++ b/base_layer/core/src/chain_storage/error.rs @@ -34,6 +34,7 @@ use crate::{ proof_of_work::PowError, transactions::transaction_components::TransactionError, validation::ValidationError, + MrHashError, }; #[derive(Debug, Error)] @@ -141,6 +142,8 @@ pub enum ChainStorageError { SMTError(#[from] SMTError), #[error("Invalid ChainMetaData: {0}")] InvalidChainMetaData(#[from] ChainMetaDataError), + #[error("Block header error: `{0}`")] + MrHashError(#[from] MrHashError), } impl ChainStorageError { @@ -193,7 +196,8 @@ impl ChainStorageError { _err @ ChainStorageError::CompositeKeyLengthExceeded | _err @ ChainStorageError::FromKeyBytesFailed(_) | _err @ ChainStorageError::InvalidChainMetaData(_) | - _err @ ChainStorageError::OutOfRange => None, + _err @ ChainStorageError::OutOfRange | + _err @ ChainStorageError::MrHashError(_) => None, } } } diff --git a/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs b/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs index cf35ee93a4..a38a1a0179 100644 --- a/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs +++ b/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs @@ -104,6 +104,7 @@ use crate::{ ValidatorNodeEntry, }, consensus::{ConsensusConstants, ConsensusManager}, + output_mr_hash_from_smt, transactions::{ aggregated_body::AggregateBody, transaction_components::{ @@ -919,7 +920,7 @@ impl LMDBDatabase { self.delete_block_inputs_outputs(write_txn, block_hash.as_slice(), &mut output_smt)?; let new_tip_header = self.fetch_chain_header_by_height(prev_height)?; - let root = FixedHash::try_from(output_smt.hash().as_slice())?; + let root = output_mr_hash_from_smt(&mut output_smt)?; if root != new_tip_header.header().output_mr { error!( target: LOG_TARGET, diff --git a/base_layer/core/src/lib.rs b/base_layer/core/src/lib.rs index a48f2d5760..03e1b86c5b 100644 --- a/base_layer/core/src/lib.rs +++ b/base_layer/core/src/lib.rs @@ -50,17 +50,22 @@ pub mod mempool; pub mod transactions; mod common; + #[cfg(feature = "base_node")] pub use common::AuxChainHashes; pub use common::{borsh, one_sided, ConfidentialOutputHasher}; #[cfg(feature = "base_node")] mod domain_hashing { + use std::convert::TryFrom; + use blake2::Blake2b; use digest::consts::U32; + use tari_common_types::types::{FixedHash, FixedHashSizeError}; use tari_crypto::{hash_domain, hashing::DomainSeparatedHasher}; use tari_hashing::ValidatorNodeBmtHashDomain; use tari_mmr::{ + error::MerkleMountainRangeError, pruned_hashset::PrunedHashSet, sparse_merkle_tree::SparseMerkleTree, BalancedBinaryMerkleTree, @@ -85,6 +90,34 @@ mod domain_hashing { pub type ValidatorNodeBmtHasherBlake256 = DomainSeparatedHasher, ValidatorNodeBmtHashDomain>; pub type ValidatorNodeBMT = BalancedBinaryMerkleTree; + + #[inline] + pub fn kernel_mr_hash_from_mmr(kernel_mmr: &KernelMmr) -> Result { + Ok(FixedHash::try_from(kernel_mmr.get_merkle_root()?)?) + } + + #[inline] + pub fn kernel_mr_hash_from_pruned_mmr(kernel_mmr: &PrunedKernelMmr) -> Result { + Ok(FixedHash::try_from(kernel_mmr.get_merkle_root()?)?) + } + + #[inline] + pub fn output_mr_hash_from_smt(output_smt: &mut OutputSmt) -> Result { + Ok(FixedHash::try_from(output_smt.hash().as_slice())?) + } + + #[inline] + pub fn input_mr_hash_from_pruned_mmr(input_mmr: &PrunedInputMmr) -> Result { + Ok(FixedHash::try_from(input_mmr.get_merkle_root()?)?) + } + + #[derive(Debug, thiserror::Error)] + pub enum MrHashError { + #[error("Output SMT conversion error: {0}")] + FixedHashSizeError(#[from] FixedHashSizeError), + #[error("Input MR conversion error: {0}")] + MerkleMountainRangeError(#[from] MerkleMountainRangeError), + } } #[cfg(feature = "base_node")] diff --git a/base_layer/core/src/validation/test.rs b/base_layer/core/src/validation/test.rs index 64e479383e..178197065d 100644 --- a/base_layer/core/src/validation/test.rs +++ b/base_layer/core/src/validation/test.rs @@ -213,8 +213,7 @@ async fn chain_balance_validation() { .with_consensus_constants(consensus_manager.consensus_constants(0).clone()) .with_pre_mine_value(total_pre_mine) .build(); - // Create a LocalNet consensus manager that uses rincewind consensus constants and has a custom rincewind genesis - // block that contains an extra pre_mine utxo + // Create a LocalNet consensus manager that uses custom genesis block that contains an extra pre_mine utxo let consensus_manager = ConsensusManagerBuilder::new(Network::LocalNet) .with_block(genesis.clone()) .add_consensus_constants(constants) diff --git a/base_layer/core/tests/helpers/block_builders.rs b/base_layer/core/tests/helpers/block_builders.rs index 3d31058a06..dc58fb1e73 100644 --- a/base_layer/core/tests/helpers/block_builders.rs +++ b/base_layer/core/tests/helpers/block_builders.rs @@ -36,6 +36,10 @@ use tari_core::{ ChainStorageError, }, consensus::{emission::Emission, ConsensusConstants, ConsensusManager}, + input_mr_hash_from_pruned_mmr, + kernel_mr_hash_from_mmr, + kernel_mr_hash_from_pruned_mmr, + output_mr_hash_from_smt, proof_of_work::{sha3x_difficulty, AccumulatedDifficulty, AchievedTargetDifficulty, Difficulty}, transactions::{ key_manager::{MemoryDbKeyManager, TransactionKeyManagerInterface, TxoStage}, @@ -56,9 +60,14 @@ use tari_core::{ }, KernelMmr, OutputSmt, + PrunedInputMmr, + PrunedKernelMmr, }; use tari_key_manager::key_manager_service::KeyManagerInterface; -use tari_mmr::sparse_merkle_tree::{NodeKey, ValueHash}; +use tari_mmr::{ + pruned_hashset::PrunedHashSet, + sparse_merkle_tree::{NodeKey, ValueHash}, +}; use tari_script::script; use tari_utilities::{hex::Hex, ByteArray}; @@ -150,8 +159,11 @@ fn print_new_genesis_block_values() { let validator_node_mr = FixedHash::try_from(vn_mr).unwrap(); // Note: An em empty MMR will have a root of `MerkleMountainRange::::null_hash()` - let kernel_mr = KernelMmr::new(Vec::new()).get_merkle_root().unwrap(); - let output_mr = FixedHash::try_from(OutputSmt::new().hash().as_slice()).unwrap(); + let kernel_mr = kernel_mr_hash_from_mmr(&KernelMmr::new(Vec::new())).unwrap(); + let kernel_mr_pruned = kernel_mr_hash_from_pruned_mmr(&PrunedKernelMmr::new(PrunedHashSet::default())).unwrap(); + assert_eq!(kernel_mr, kernel_mr_pruned); + let input_mr = input_mr_hash_from_pruned_mmr(&PrunedInputMmr::new(PrunedHashSet::default())).unwrap(); + let output_mr = output_mr_hash_from_smt(&mut OutputSmt::new()).unwrap(); // Note: This is printed in the same order as needed for 'fn get_xxxx_genesis_block_raw()' println!(); @@ -162,6 +174,7 @@ fn print_new_genesis_block_values() { println!("header kernel_mr: {}", kernel_mr.to_hex()); println!("header kernel_mmr_size: 0"); println!("header validator_node_mr: {}", validator_node_mr.to_hex()); + println!("header input_mr: {}", input_mr.to_hex()); println!("header total_kernel_offset: {}", FixedHash::zero().to_hex()); println!("header total_script_offset: {}", FixedHash::zero().to_hex()); } @@ -191,7 +204,7 @@ fn update_genesis_block_mmr_roots(template: NewBlockTemplate) -> Result