From 69a5872e52d8332e0b46e50614b2105bed0d22c9 Mon Sep 17 00:00:00 2001 From: SW van Heerden Date: Thu, 14 Nov 2024 10:14:44 +0200 Subject: [PATCH] feat!: add new header field (#6686) Description --- Add new header field, block output mmr which only tracks outputs in current block Motivation and Context --- This is needed in order to validate that all outputs included in the block is correctly captured in the pow of the header. The current strategy is to only use the output smt, which commits to all outputs on the blockchain. This is required for horizon sync and prune mode to verify the blockchain as we sync. But someone else wants to verify the block outputs, they have to have the entire outputs set aka entire blockchain in order to verify that the outputs are indeed locked behind pow. This addition allows any person to take only the block and know that the outputs contained in that block, is correctly locked behind the pow of the header. The output field is constructed as two merkle mountain ranges. the primary one used in the header field is constructed as [coinbase_1, coinbase_2, .., coinbase_x, non_coinbase_ouputs_merkle_root] with the non_coinbase_ouputs_merkle_root constructed as [output_1, output_2, .., output_x] This double mountain range approach allows you to prove the output are in the block header, as well as create a proof for a specific output. But separating them allows p2pool to only need to store a single hash + list of all outputs in order to verify all coin bases are correctly included in the header. Rather than all outputs or before this pr, the entire block. Breaking change --- Need to resync database --- .../minotari_app_grpc/proto/block.proto | 5 +- .../src/conversions/block_header.rs | 2 + .../base_node/sync/header_sync/validator.rs | 14 ++++-- base_layer/core/src/blocks/block_header.rs | 19 +++++--- base_layer/core/src/blocks/genesis_block.rs | 47 +++++++++++++++++-- .../src/chain_storage/blockchain_database.rs | 20 ++++++++ .../core/src/consensus/consensus_constants.rs | 31 +++++++----- base_layer/core/src/lib.rs | 6 +++ .../src/proof_of_work/monero_rx/helpers.rs | 9 ++++ .../core/src/proof_of_work/sha3x_pow.rs | 2 +- base_layer/core/src/proto/block.proto | 5 +- base_layer/core/src/proto/block_header.rs | 2 + base_layer/core/src/test_helpers/mod.rs | 1 + .../core/src/validation/block_body/test.rs | 4 ++ base_layer/core/src/validation/helpers.rs | 18 ++++++- .../core/tests/helpers/block_builders.rs | 4 ++ .../core/tests/tests/block_validation.rs | 2 +- 17 files changed, 159 insertions(+), 32 deletions(-) diff --git a/applications/minotari_app_grpc/proto/block.proto b/applications/minotari_app_grpc/proto/block.proto index f5ed70c3ee..afb6d7c855 100644 --- a/applications/minotari_app_grpc/proto/block.proto +++ b/applications/minotari_app_grpc/proto/block.proto @@ -38,9 +38,10 @@ message BlockHeader { bytes prev_hash = 4; // Timestamp at which the block was built. uint64 timestamp = 5; - // This is the UTXO merkle root of the outputs - // This is calculated as Hash (txo MMR root || roaring bitmap hash of UTXO indices) + // This is the UTXO merkle root of the outputs in the blockchain bytes output_mr = 6; + // This is the merkle root of all outputs in this block + bytes block_output_mr = 7; // This is the MMR root of the kernels bytes kernel_mr = 8; // This is the Merkle root of the inputs in this block diff --git a/applications/minotari_app_grpc/src/conversions/block_header.rs b/applications/minotari_app_grpc/src/conversions/block_header.rs index a0eb9e05cc..dccee203e5 100644 --- a/applications/minotari_app_grpc/src/conversions/block_header.rs +++ b/applications/minotari_app_grpc/src/conversions/block_header.rs @@ -39,6 +39,7 @@ impl From for grpc::BlockHeader { timestamp: h.timestamp.as_u64(), input_mr: h.input_mr.to_vec(), output_mr: h.output_mr.to_vec(), + block_output_mr: h.block_output_mr.to_vec(), output_mmr_size: h.output_smt_size, kernel_mr: h.kernel_mr.to_vec(), kernel_mmr_size: h.kernel_mmr_size, @@ -76,6 +77,7 @@ impl TryFrom for BlockHeader { timestamp: EpochTime::from(header.timestamp), input_mr: FixedHash::try_from(header.input_mr).map_err(|err| err.to_string())?, output_mr: FixedHash::try_from(header.output_mr).map_err(|err| err.to_string())?, + block_output_mr: FixedHash::try_from(header.block_output_mr).unwrap_or_default(), output_smt_size: header.output_mmr_size, kernel_mr: FixedHash::try_from(header.kernel_mr).map_err(|err| err.to_string())?, kernel_mmr_size: header.kernel_mmr_size, diff --git a/base_layer/core/src/base_node/sync/header_sync/validator.rs b/base_layer/core/src/base_node/sync/header_sync/validator.rs index 6a60cc81b7..bcf8d6cc05 100644 --- a/base_layer/core/src/base_node/sync/header_sync/validator.rs +++ b/base_layer/core/src/base_node/sync/header_sync/validator.rs @@ -245,13 +245,18 @@ mod test { test_helpers::blockchain::{create_new_blockchain, TempDatabase}, }; - fn setup() -> (BlockHeaderSyncValidator, AsyncBlockchainDb) { + fn setup() -> ( + BlockHeaderSyncValidator, + AsyncBlockchainDb, + ConsensusManager, + ) { let rules = ConsensusManager::builder(Network::LocalNet).build().unwrap(); let randomx_factory = RandomXFactory::default(); let db = create_new_blockchain(); ( - BlockHeaderSyncValidator::new(db.clone().into(), rules, randomx_factory), + BlockHeaderSyncValidator::new(db.clone().into(), rules.clone(), randomx_factory), db.into(), + rules, ) } @@ -262,10 +267,11 @@ mod test { AsyncBlockchainDb, ChainHeader, ) { - let (validator, db) = setup(); + let (validator, db, cm) = setup(); let mut tip = db.fetch_tip_header().await.unwrap(); for _ in 0..n { let mut header = BlockHeader::from_previous(tip.header()); + header.version = cm.consensus_constants(header.height).blockchain_version(); // Needed to have unique keys for the blockchain db mmr count indexes (MDB_KEY_EXIST error) header.kernel_mmr_size += 1; header.output_smt_size += 1; @@ -301,7 +307,7 @@ mod test { #[tokio::test] async fn it_errors_if_hash_does_not_exist() { - let (mut validator, _) = setup(); + let (mut validator, _, _cm) = setup(); let start_hash = vec![0; 32]; let err = validator .initialize_state(&start_hash.clone().try_into().unwrap()) diff --git a/base_layer/core/src/blocks/block_header.rs b/base_layer/core/src/blocks/block_header.rs index 5b5704348c..4988a38e9e 100644 --- a/base_layer/core/src/blocks/block_header.rs +++ b/base_layer/core/src/blocks/block_header.rs @@ -98,9 +98,10 @@ pub struct BlockHeader { pub timestamp: EpochTime, /// This is the Merkle root of the inputs in this block pub input_mr: FixedHash, - /// This is the UTXO merkle root of the outputs - /// This is calculated as Hash (txo MMR root || roaring bitmap hash of UTXO indices) + /// This is the UTXO merkle root of the outputs on the blockchain pub output_mr: FixedHash, + /// This is the block_output_mr + pub block_output_mr: FixedHash, /// The size (number of leaves) of the output and range proof MMRs at the time of this header pub output_smt_size: u64, /// This is the MMR root of the kernels @@ -130,6 +131,7 @@ impl BlockHeader { prev_hash: FixedHash::zero(), timestamp: EpochTime::now(), output_mr: FixedHash::zero(), + block_output_mr: FixedHash::zero(), output_smt_size: 0, kernel_mr: FixedHash::zero(), kernel_mmr_size: 0, @@ -164,6 +166,7 @@ impl BlockHeader { timestamp: EpochTime::now(), output_mr: FixedHash::zero(), output_smt_size: prev.output_smt_size, + block_output_mr: FixedHash::zero(), kernel_mr: FixedHash::zero(), kernel_mmr_size: prev.kernel_mmr_size, input_mr: FixedHash::zero(), @@ -222,7 +225,7 @@ impl BlockHeader { /// Provides a mining hash of the header, used for the mining. /// This differs from the normal hash by not hashing the nonce and kernel pow. pub fn mining_hash(&self) -> FixedHash { - DomainSeparatedConsensusHasher::>::new("block_header") + let incomplete = DomainSeparatedConsensusHasher::>::new("block_header") .chain(&self.version) .chain(&self.height) .chain(&self.prev_hash) @@ -235,9 +238,12 @@ impl BlockHeader { .chain(&self.total_kernel_offset) .chain(&self.total_script_offset) .chain(&self.validator_node_mr) - .chain(&self.validator_node_size) - .finalize() - .into() + .chain(&self.validator_node_size); + + match self.version { + 0 => incomplete.finalize().into(), + _ => incomplete.chain(&self.block_output_mr).finalize().into(), + } } pub fn merge_mining_hash(&self) -> FixedHash { @@ -273,6 +279,7 @@ impl From for BlockHeader { prev_hash: header_template.prev_hash, timestamp: EpochTime::now(), output_mr: FixedHash::zero(), + block_output_mr: FixedHash::zero(), output_smt_size: 0, kernel_mr: FixedHash::zero(), kernel_mmr_size: 0, diff --git a/base_layer/core/src/blocks/genesis_block.rs b/base_layer/core/src/blocks/genesis_block.rs index 5ca7cdf802..8818206ca2 100644 --- a/base_layer/core/src/blocks/genesis_block.rs +++ b/base_layer/core/src/blocks/genesis_block.rs @@ -191,6 +191,8 @@ pub fn get_nextnet_genesis_block() -> ChainBlock { // TODO: Fix this hack with the next nextnet reset!! block.header.input_mr = FixedHash::from_hex("0000000000000000000000000000000000000000000000000000000000000000").unwrap(); + block.header.block_output_mr = + FixedHash::from_hex("0000000000000000000000000000000000000000000000000000000000000000").unwrap(); // Add pre-mine utxos - enable/disable as required let add_pre_mine_utxos = false; @@ -270,6 +272,8 @@ pub fn get_mainnet_genesis_block() -> ChainBlock { FixedHash::from_hex("b7b38b76f5832b5b63691a8334dfa67d8c762b77b2b4aa4f648c4eb1dfb25c1e").unwrap(); block.header.output_mr = FixedHash::from_hex("a77ecf05b20c426d3d400a63397be6c622843c66d5751ecbe3390c8a4885158e").unwrap(); + block.header.block_output_mr = + FixedHash::from_hex("91e997520b0eee770914334692080f92d18db434d373561f8842c56d70c11b97").unwrap(); block.header.validator_node_mr = FixedHash::from_hex("277da65c40b2cf99db86baedb903a3f0a38540f3a94d40c826eecac7e27d5dfc").unwrap(); } @@ -371,6 +375,10 @@ pub fn get_esmeralda_genesis_block() -> ChainBlock { // lets get the block let mut block = get_esmeralda_genesis_block_raw(); + // TODO: Fix this hack with the next esme reset!! + block.header.block_output_mr = + FixedHash::from_hex("0000000000000000000000000000000000000000000000000000000000000000").unwrap(); + // Add pre-mine utxos - enable/disable as required let add_pre_mine_utxos = true; if add_pre_mine_utxos { @@ -482,7 +490,9 @@ fn get_raw_block(genesis_timestamp: &DateTime, not_before_proof: &P height: 0, prev_hash: FixedHash::zero(), timestamp: timestamp.into(), - output_mr: FixedHash::from_hex("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), + output_mr: FixedHash::zero(), + block_output_mr: FixedHash::from_hex("622720a6571c33d6bf6138d9e737d3468c77f1193640698ad459953d24ec0812") + .unwrap(), output_smt_size: 0, kernel_mr: FixedHash::from_hex("c14803066909d6d22abf0d2d2782e8936afc3f713f2af3a4ef5c42e8400c1303").unwrap(), kernel_mmr_size: 0, @@ -518,6 +528,7 @@ mod test { use super::*; use crate::{ + block_output_mr_hash_from_pruned_mmr, chain_storage::calculate_validator_node_mr, consensus::ConsensusManager, test_helpers::blockchain::create_new_blockchain_with_network, @@ -527,8 +538,8 @@ mod test { }, validation::{ChainBalanceValidator, FinalHorizonStateValidation}, KernelMmr, + PrunedOutputMmr, }; - #[test] #[serial] fn esmeralda_genesis_sanity_check() { @@ -661,9 +672,15 @@ mod test { kernel_mmr.push(k.hash().to_vec()).unwrap(); } let mut output_smt = OutputSmt::new(); - + let mut block_output_mmr = PrunedOutputMmr::new(PrunedHashSet::default()); + let mut normal_output_mmr = PrunedOutputMmr::new(PrunedHashSet::default()); let mut vn_nodes = Vec::new(); for o in block.block().body.outputs() { + if o.features.is_coinbase() { + block_output_mmr.push(o.hash().to_vec()).unwrap(); + } else { + normal_output_mmr.push(o.hash().to_vec()).unwrap(); + } let smt_key = NodeKey::try_from(o.commitment.as_bytes()).unwrap(); let smt_node = ValueHash::try_from(o.smt_hash(block.header().height).as_slice()).unwrap(); output_smt.insert(smt_key, smt_node).unwrap(); @@ -681,6 +698,11 @@ mod test { )); } } + + block_output_mmr + .push(normal_output_mmr.get_merkle_root().unwrap().to_vec()) + .unwrap(); + 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(); @@ -718,6 +740,25 @@ mod test { output_mr_hash_from_smt(&mut output_smt).unwrap().to_vec().to_hex(), block.header().output_mr.to_vec().to_hex(), ); + + // TODO: Fix this hack with the next nextnet/esme release reset!! + if network == Network::NextNet || network == Network::Esmeralda { + assert_eq!( + FixedHash::from_hex("0000000000000000000000000000000000000000000000000000000000000000") + .unwrap() + .to_vec() + .to_hex(), + block.header().block_output_mr.to_vec().to_hex(), + ); + } else { + assert_eq!( + block_output_mr_hash_from_pruned_mmr(&block_output_mmr) + .unwrap() + .to_vec() + .to_hex(), + block.header().block_output_mr.to_vec().to_hex(), + ); + } if network == Network::NextNet { // TODO: Fix this hack with the next nextnet reset!! assert_eq!( diff --git a/base_layer/core/src/chain_storage/blockchain_database.rs b/base_layer/core/src/chain_storage/blockchain_database.rs index 2f00871ac3..85b51e6b25 100644 --- a/base_layer/core/src/chain_storage/blockchain_database.rs +++ b/base_layer/core/src/chain_storage/blockchain_database.rs @@ -49,6 +49,7 @@ use tari_utilities::{epoch_time::EpochTime, hex::Hex, ByteArray}; use super::TemplateRegistrationEntry; use crate::{ + block_output_mr_hash_from_pruned_mmr, blocks::{ Block, BlockAccumulatedData, @@ -105,6 +106,7 @@ use crate::{ OutputSmt, PrunedInputMmr, PrunedKernelMmr, + PrunedOutputMmr, ValidatorNodeBMT, }; @@ -912,6 +914,7 @@ where B: BlockchainBackend block.header.kernel_mmr_size = roots.kernel_mmr_size; block.header.input_mr = roots.input_mr; block.header.output_mr = roots.output_mr; + block.header.block_output_mr = roots.block_output_mr; block.header.output_smt_size = roots.output_smt_size; block.header.validator_node_mr = roots.validator_node_mr; block.header.validator_node_size = roots.validator_node_size; @@ -1321,6 +1324,7 @@ pub struct MmrRoots { pub kernel_mmr_size: u64, pub input_mr: FixedHash, pub output_mr: FixedHash, + pub block_output_mr: FixedHash, pub output_smt_size: u64, pub validator_node_mr: FixedHash, pub validator_node_size: u64, @@ -1333,6 +1337,7 @@ impl std::fmt::Display for MmrRoots { writeln!(f, "Kernel MR : {}", self.kernel_mr)?; writeln!(f, "Kernel MMR Size : {}", self.kernel_mmr_size)?; writeln!(f, "Output MR : {}", self.output_mr)?; + writeln!(f, "Block Output MR : {}", self.block_output_mr)?; writeln!(f, "Output SMT Size : {}", self.output_smt_size)?; writeln!(f, "Validator MR : {}", self.validator_node_mr)?; Ok(()) @@ -1372,6 +1377,8 @@ pub fn calculate_mmr_roots( let mut kernel_mmr = PrunedKernelMmr::new(kernels); let mut input_mmr = PrunedInputMmr::new(PrunedHashSet::default()); + let mut block_output_mmr = PrunedOutputMmr::new(PrunedHashSet::default()); + let mut normal_output_mmr = PrunedOutputMmr::new(PrunedHashSet::default()); for kernel in body.kernels() { kernel_mmr.push(kernel.hash().to_vec())?; @@ -1379,6 +1386,11 @@ pub fn calculate_mmr_roots( let mut outputs_to_remove = Vec::new(); for output in body.outputs() { + if output.features.is_coinbase() { + block_output_mmr.push(output.hash().to_vec())?; + } else { + normal_output_mmr.push(output.hash().to_vec())?; + } if !output.is_burned() { let smt_key = NodeKey::try_from(output.commitment.as_bytes())?; let smt_node = ValueHash::try_from(output.smt_hash(header.height).as_slice())?; @@ -1393,6 +1405,7 @@ pub fn calculate_mmr_roots( } } } + block_output_mmr.push(normal_output_mmr.get_merkle_root()?.to_vec())?; let mut outputs_to_add = Vec::new(); for input in body.inputs() { @@ -1426,11 +1439,18 @@ pub fn calculate_mmr_roots( (tip_header.validator_node_mr, 0) }; + let block_output_mr = if block.version() > 0 { + block_output_mr_hash_from_pruned_mmr(&block_output_mmr)? + } else { + FixedHash::zero() + }; + let mmr_roots = MmrRoots { kernel_mr: kernel_mr_hash_from_pruned_mmr(&kernel_mmr)?, kernel_mmr_size: kernel_mmr.get_leaf_count()? as u64, input_mr: input_mr_hash_from_pruned_mmr(&input_mmr)?, output_mr: output_mr_hash_from_smt(output_smt)?, + block_output_mr, output_smt_size: output_smt.size(), validator_node_mr, validator_node_size: validator_node_size as u64, diff --git a/base_layer/core/src/consensus/consensus_constants.rs b/base_layer/core/src/consensus/consensus_constants.rs index 7762f94dc2..796a68f728 100644 --- a/base_layer/core/src/consensus/consensus_constants.rs +++ b/base_layer/core/src/consensus/consensus_constants.rs @@ -389,8 +389,8 @@ impl ConsensusConstants { let consensus_constants = vec![ConsensusConstants { effective_from_height: 0, coinbase_min_maturity: 2, - blockchain_version: 0, - valid_blockchain_version_range: 0..=0, + blockchain_version: 1, + valid_blockchain_version_range: 1..=1, future_time_limit: 540, difficulty_block_window, max_block_transaction_weight: 19500, @@ -509,7 +509,7 @@ impl ConsensusConstants { target_time: 240, }); let (input_version_range, output_version_range, kernel_version_range) = version_zero(); - let consensus_constants = vec![ConsensusConstants { + let consensus_constants1 = ConsensusConstants { effective_from_height: 0, coinbase_min_maturity: 6, blockchain_version: 0, @@ -541,9 +541,14 @@ impl ConsensusConstants { vn_registration_lock_height: 0, vn_registration_shuffle_interval: VnEpoch(100), coinbase_output_features_extra_max_length: 256, - }]; + }; + let mut consensus_constants2 = consensus_constants1.clone(); + consensus_constants2.blockchain_version = 1; + consensus_constants2.effective_from_height = 16000; + consensus_constants2.valid_blockchain_version_range = 1..=1; + let consensus_constants = vec![consensus_constants1, consensus_constants2]; #[cfg(any(test, debug_assertions))] - assert_hybrid_pow_constants(&consensus_constants, &[120], &[50], &[50]); + assert_hybrid_pow_constants(&consensus_constants, &[120, 120], &[50, 50], &[50, 50]); consensus_constants } @@ -569,8 +574,8 @@ impl ConsensusConstants { let consensus_constants = vec![ConsensusConstants { effective_from_height: 0, coinbase_min_maturity: 360, - blockchain_version: 0, - valid_blockchain_version_range: 0..=0, + blockchain_version: 1, + valid_blockchain_version_range: 1..=1, future_time_limit: 540, difficulty_block_window: 90, max_block_transaction_weight: 127_795, @@ -653,10 +658,14 @@ impl ConsensusConstants { let mut con_2 = con_1.clone(); con_2.effective_from_height = 33000; con_2.coinbase_output_features_extra_max_length = 256; + let mut con_3 = con_2.clone(); + con_3.effective_from_height = 52000; + con_3.blockchain_version = 1; + con_3.valid_blockchain_version_range = 1..=1; - let consensus_constants = vec![con_1, con_2]; + let consensus_constants = vec![con_1, con_2, con_3]; #[cfg(any(test, debug_assertions))] - assert_hybrid_pow_constants(&consensus_constants, &[120, 120], &[50, 50], &[50, 50]); + assert_hybrid_pow_constants(&consensus_constants, &[120, 120, 120], &[50, 50, 50], &[50, 50, 50]); consensus_constants } @@ -678,8 +687,8 @@ impl ConsensusConstants { let consensus_constants = vec![ConsensusConstants { effective_from_height: 0, coinbase_min_maturity: 720, - blockchain_version: 0, - valid_blockchain_version_range: 0..=0, + blockchain_version: 1, + valid_blockchain_version_range: 1..=1, future_time_limit: 540, difficulty_block_window, max_block_transaction_weight: 127_795, diff --git a/base_layer/core/src/lib.rs b/base_layer/core/src/lib.rs index 03e1b86c5b..5c8812467c 100644 --- a/base_layer/core/src/lib.rs +++ b/base_layer/core/src/lib.rs @@ -85,6 +85,7 @@ mod domain_hashing { hash_domain!(InputMmrHashDomain, "com.tari.base_layer.core.input_mmr", 1); pub type InputMmrHasherBlake256 = DomainSeparatedHasher, InputMmrHashDomain>; pub type PrunedInputMmr = MerkleMountainRange; + pub type PrunedOutputMmr = MerkleMountainRange; pub type OutputSmt = SparseMerkleTree; @@ -111,6 +112,11 @@ mod domain_hashing { Ok(FixedHash::try_from(input_mmr.get_merkle_root()?)?) } + #[inline] + pub fn block_output_mr_hash_from_pruned_mmr(output_mmr: &PrunedOutputMmr) -> Result { + Ok(FixedHash::try_from(output_mmr.get_merkle_root()?)?) + } + #[derive(Debug, thiserror::Error)] pub enum MrHashError { #[error("Output SMT conversion error: {0}")] 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 100e09c4b7..7f57275e38 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 @@ -550,6 +550,7 @@ mod test { prev_hash: FixedHash::zero(), timestamp: EpochTime::now(), output_mr: FixedHash::zero(), + block_output_mr: FixedHash::zero(), output_smt_size: 0, kernel_mr: FixedHash::zero(), kernel_mmr_size: 0, @@ -652,6 +653,7 @@ mod test { prev_hash: FixedHash::zero(), timestamp: EpochTime::now(), output_mr: FixedHash::zero(), + block_output_mr: FixedHash::zero(), output_smt_size: 0, kernel_mr: FixedHash::zero(), kernel_mmr_size: 0, @@ -726,6 +728,7 @@ mod test { prev_hash: FixedHash::zero(), timestamp: EpochTime::now(), output_mr: FixedHash::zero(), + block_output_mr: FixedHash::zero(), output_smt_size: 0, kernel_mr: FixedHash::zero(), kernel_mmr_size: 0, @@ -798,6 +801,7 @@ mod test { prev_hash: FixedHash::zero(), timestamp: EpochTime::now(), output_mr: FixedHash::zero(), + block_output_mr: FixedHash::zero(), output_smt_size: 0, kernel_mr: FixedHash::zero(), kernel_mmr_size: 0, @@ -874,6 +878,7 @@ mod test { prev_hash: FixedHash::zero(), timestamp: EpochTime::now(), output_mr: FixedHash::zero(), + block_output_mr: FixedHash::zero(), output_smt_size: 0, kernel_mr: FixedHash::zero(), kernel_mmr_size: 0, @@ -970,6 +975,7 @@ mod test { prev_hash: FixedHash::zero(), timestamp: EpochTime::now(), output_mr: FixedHash::zero(), + block_output_mr: FixedHash::zero(), output_smt_size: 0, kernel_mr: FixedHash::zero(), kernel_mmr_size: 0, @@ -1030,6 +1036,7 @@ mod test { prev_hash: FixedHash::zero(), timestamp: EpochTime::now(), output_mr: FixedHash::zero(), + block_output_mr: FixedHash::zero(), output_smt_size: 0, kernel_mr: FixedHash::zero(), kernel_mmr_size: 0, @@ -1102,6 +1109,7 @@ mod test { prev_hash: FixedHash::zero(), timestamp: EpochTime::now(), output_mr: FixedHash::zero(), + block_output_mr: FixedHash::zero(), output_smt_size: 0, kernel_mr: FixedHash::zero(), kernel_mmr_size: 0, @@ -1163,6 +1171,7 @@ mod test { prev_hash: FixedHash::zero(), timestamp: EpochTime::now(), output_mr: FixedHash::zero(), + block_output_mr: FixedHash::zero(), output_smt_size: 0, kernel_mr: FixedHash::zero(), kernel_mmr_size: 0, diff --git a/base_layer/core/src/proof_of_work/sha3x_pow.rs b/base_layer/core/src/proof_of_work/sha3x_pow.rs index c1953826b2..af788c3288 100644 --- a/base_layer/core/src/proof_of_work/sha3x_pow.rs +++ b/base_layer/core/src/proof_of_work/sha3x_pow.rs @@ -101,6 +101,6 @@ pub mod test { let mut header = get_header(); header.nonce = 631; println!("{:?}", header); - assert_eq!(sha3x_difficulty(&header).unwrap(), Difficulty::from_u64(28).unwrap()); + assert_eq!(sha3x_difficulty(&header).unwrap(), Difficulty::from_u64(13).unwrap()); } } diff --git a/base_layer/core/src/proto/block.proto b/base_layer/core/src/proto/block.proto index 9636958120..184a8d3c53 100644 --- a/base_layer/core/src/proto/block.proto +++ b/base_layer/core/src/proto/block.proto @@ -31,9 +31,10 @@ message BlockHeader { bytes prev_hash = 4; // Timestamp at which the block was built. uint64 timestamp = 5; - // This is the UTXO merkle root of the outputs - // This is calculated as Hash (txo MMR root || roaring bitmap hash of UTXO indices) + // This is the UTXO merkle root of the outputs in the blockchain bytes output_mr = 6; + // This is the merkle root of all outputs in this block + bytes block_output_mr = 7; // This is the MMR root of the kernels bytes kernel_mr = 8; // This is the Merkle root of the inputs in this block diff --git a/base_layer/core/src/proto/block_header.rs b/base_layer/core/src/proto/block_header.rs index 3503657990..98303b34ba 100644 --- a/base_layer/core/src/proto/block_header.rs +++ b/base_layer/core/src/proto/block_header.rs @@ -52,6 +52,7 @@ impl TryFrom for BlockHeader { prev_hash: FixedHash::try_from(header.prev_hash).map_err(|err| err.to_string())?, timestamp: EpochTime::from(header.timestamp), output_mr: FixedHash::try_from(header.output_mr).map_err(|err| err.to_string())?, + block_output_mr: FixedHash::try_from(header.block_output_mr).unwrap_or_default(), output_smt_size: header.output_mmr_size, kernel_mr: FixedHash::try_from(header.kernel_mr).map_err(|err| err.to_string())?, kernel_mmr_size: header.kernel_mmr_size, @@ -74,6 +75,7 @@ impl From for proto::BlockHeader { prev_hash: header.prev_hash.to_vec(), timestamp: header.timestamp.as_u64(), output_mr: header.output_mr.to_vec(), + block_output_mr: header.block_output_mr.to_vec(), kernel_mr: header.kernel_mr.to_vec(), input_mr: header.input_mr.to_vec(), total_kernel_offset: header.total_kernel_offset.to_vec(), diff --git a/base_layer/core/src/test_helpers/mod.rs b/base_layer/core/src/test_helpers/mod.rs index a8677fbdda..24bb2d67a8 100644 --- a/base_layer/core/src/test_helpers/mod.rs +++ b/base_layer/core/src/test_helpers/mod.rs @@ -95,6 +95,7 @@ pub async fn create_block( range_proof_type: Option, ) -> (Block, WalletOutput) { let mut header = BlockHeader::from_previous(&prev_block.header); + header.version = rules.consensus_constants(header.height).blockchain_version(); let block_height = spec.height_override.unwrap_or(prev_block.header.height + 1); header.height = block_height; let reward = spec.reward_override.unwrap_or_else(|| { diff --git a/base_layer/core/src/validation/block_body/test.rs b/base_layer/core/src/validation/block_body/test.rs index fede6a9984..1afa2f3f66 100644 --- a/base_layer/core/src/validation/block_body/test.rs +++ b/base_layer/core/src/validation/block_body/test.rs @@ -94,6 +94,7 @@ async fn it_passes_if_large_output_block_is_valid() { .calculate_mmr_roots(chain_block.block().clone()) .unwrap(); block.header.input_mr = mmr_roots.input_mr; + block.header.block_output_mr = mmr_roots.block_output_mr; block.header.output_mr = mmr_roots.output_mr; block.header.output_smt_size = mmr_roots.output_smt_size; block.header.kernel_mr = mmr_roots.kernel_mr; @@ -131,6 +132,7 @@ async fn it_validates_when_a_coinbase_is_spent() { .unwrap(); block.header.input_mr = mmr_roots.input_mr; block.header.output_mr = mmr_roots.output_mr; + block.header.block_output_mr = mmr_roots.block_output_mr; block.header.output_smt_size = mmr_roots.output_smt_size; block.header.kernel_mr = mmr_roots.kernel_mr; block.header.kernel_mmr_size = mmr_roots.kernel_mmr_size; @@ -173,6 +175,7 @@ async fn it_passes_if_large_block_is_valid() { .unwrap(); block.header.input_mr = mmr_roots.input_mr; block.header.output_mr = mmr_roots.output_mr; + block.header.block_output_mr = mmr_roots.block_output_mr; block.header.output_smt_size = mmr_roots.output_smt_size; block.header.kernel_mr = mmr_roots.kernel_mr; block.header.kernel_mmr_size = mmr_roots.kernel_mmr_size; @@ -203,6 +206,7 @@ async fn it_passes_if_block_is_valid() { .unwrap(); block.header.input_mr = mmr_roots.input_mr; block.header.output_mr = mmr_roots.output_mr; + block.header.block_output_mr = mmr_roots.block_output_mr; block.header.output_smt_size = mmr_roots.output_smt_size; block.header.kernel_mr = mmr_roots.kernel_mr; block.header.kernel_mmr_size = mmr_roots.kernel_mmr_size; diff --git a/base_layer/core/src/validation/helpers.rs b/base_layer/core/src/validation/helpers.rs index 160706defe..489ee2beff 100644 --- a/base_layer/core/src/validation/helpers.rs +++ b/base_layer/core/src/validation/helpers.rs @@ -263,6 +263,7 @@ pub fn check_not_duplicate_txo( Ok(()) } +#[allow(clippy::too_many_lines)] pub fn check_mmr_roots(header: &BlockHeader, mmr_roots: &MmrRoots) -> Result<(), ValidationError> { if header.kernel_mr != mmr_roots.kernel_mr { warn!( @@ -302,7 +303,7 @@ pub fn check_mmr_roots(header: &BlockHeader, mmr_roots: &MmrRoots) -> Result<(), mmr_roots.output_mr.to_hex() ); return Err(ValidationError::BlockError(BlockValidationError::MismatchedMmrRoots { - kind: "Utxo", + kind: "Utxos", })); }; if header.output_smt_size != mmr_roots.output_smt_size { @@ -318,7 +319,20 @@ pub fn check_mmr_roots(header: &BlockHeader, mmr_roots: &MmrRoots) -> Result<(), expected: mmr_roots.output_smt_size, actual: header.output_smt_size, })); - } + }; + if header.block_output_mr != mmr_roots.block_output_mr { + warn!( + target: LOG_TARGET, + "Block header block output MMR roots in #{} {} do not match calculated roots. Expected: {}, Actual:{}", + header.height, + header.hash().to_hex(), + header.block_output_mr, + mmr_roots.block_output_mr, + ); + return Err(ValidationError::BlockError(BlockValidationError::MismatchedMmrRoots { + kind: "block outputs", + })); + }; if header.input_mr != mmr_roots.input_mr { warn!( target: LOG_TARGET, diff --git a/base_layer/core/tests/helpers/block_builders.rs b/base_layer/core/tests/helpers/block_builders.rs index dc58fb1e73..6827a9048f 100644 --- a/base_layer/core/tests/helpers/block_builders.rs +++ b/base_layer/core/tests/helpers/block_builders.rs @@ -62,6 +62,7 @@ use tari_core::{ OutputSmt, PrunedInputMmr, PrunedKernelMmr, + PrunedOutputMmr, }; use tari_key_manager::key_manager_service::KeyManagerInterface; use tari_mmr::{ @@ -206,7 +207,9 @@ fn update_genesis_block_mmr_roots(template: NewBlockTemplate) -> Result Result