From d642d6c4ef28b6f9884a8ee9744fee9dfc86ae80 Mon Sep 17 00:00:00 2001 From: SW van Heerden Date: Wed, 27 Nov 2024 09:20:51 +0200 Subject: [PATCH] feat!: refactor difficulty handling (#186) Description --- Completely refactor how we handle the backend and how we handle difficulty. --- src/server/grpc/p2pool.rs | 2 +- src/server/mod.rs | 2 +- src/server/p2p/network.rs | 114 ++- src/sharechain/error.rs | 6 +- src/sharechain/in_memory.rs | 222 +++--- src/sharechain/mod.rs | 6 +- src/sharechain/p2block.rs | 112 ++- src/sharechain/p2chain.rs | 1244 +++++++++++++++++-------------- src/sharechain/p2chain_level.rs | 30 +- 9 files changed, 946 insertions(+), 792 deletions(-) diff --git a/src/server/grpc/p2pool.rs b/src/server/grpc/p2pool.rs index d3345ba..8bec11b 100644 --- a/src/server/grpc/p2pool.rs +++ b/src/server/grpc/p2pool.rs @@ -147,7 +147,7 @@ where S: ShareChain .map(|block| Arc::::unwrap_or_clone(block)) .collect(); new_blocks.append(&mut uncles); - if new_tip { + if new_tip.new_tip.is_some() { let total_pow = share_chain.get_total_chain_pow().await; let notify = NotifyNewTipBlock::new(self.local_peer_id, new_blocks, total_pow); let res = self diff --git a/src/server/mod.rs b/src/server/mod.rs index aebb83b..1bef0bf 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -12,4 +12,4 @@ pub mod grpc; pub mod http; pub mod p2p; -pub const PROTOCOL_VERSION: u64 = 22; +pub const PROTOCOL_VERSION: u64 = 23; diff --git a/src/server/p2p/network.rs b/src/server/p2p/network.rs index c706607..3a720d0 100644 --- a/src/server/p2p/network.rs +++ b/src/server/p2p/network.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: BSD-3-Clause use std::{ - collections::{HashMap, HashSet}, + collections::HashMap, fmt::Display, fs, hash::Hash, @@ -96,6 +96,7 @@ use crate::{ }, sharechain::{ p2block::{P2Block, CURRENT_CHAIN_ID}, + p2chain::ChainAddResult, ShareChain, }, }; @@ -442,7 +443,6 @@ where S: ShareChain /// Broadcasting a new mined [`Block`] to the network (assume it is already validated with the network). async fn broadcast_block(&mut self, result: Result) { - dbg!("Broadcast block"); // if self.sync_in_progress.load(Ordering::SeqCst) { // return; // } @@ -668,32 +668,30 @@ where S: ShareChain let blocks: Vec<_> = blocks.into_iter().map(Arc::new).collect(); match share_chain.add_synced_blocks(&blocks).await { Ok(new_tip) => { - info!(target: LOG_TARGET, squad = &self.config.squad; "[{:?}]New tip notify blocks added to share chain, new tip added [{}]", algo, new_tip); - }, - Err(crate::sharechain::error::ShareChainError::BlockParentDoesNotExist { - missing_parents, - }) => { - let num_missing_parents = missing_parents.len(); - if num_missing_parents > 5 { - info!(target: LOG_TARGET, squad = &self.config.squad; "We are missing more than 5 blocks, we are missing: {}", num_missing_parents); - return Ok(MessageAcceptance::Accept); - } - - if our_tip > max_payload_height.saturating_sub(10) || - our_tip < max_payload_height.saturating_add(5) - { - info!(target: LOG_TARGET, squad = &self.config.squad; "Our tip({}) is too far off their new block({}) waiting for sync", our_tip, max_payload_height); - return Ok(MessageAcceptance::Accept); + info!(target: LOG_TARGET, squad = &self.config.squad; "[{:?}]New tip notify blocks added to share chain: {}", algo, new_tip); + let missing_parents = new_tip.to_missing_parents_vec(); + if !missing_parents.is_empty() { + if missing_parents.len() > 5 { + info!(target: LOG_TARGET, squad = &self.config.squad; "We are missing more than 5 blocks, we are missing: {}", missing_parents.len()); + return Ok(MessageAcceptance::Accept); + } + + if our_tip > max_payload_height.saturating_sub(10) || + our_tip < max_payload_height.saturating_add(5) + { + info!(target: LOG_TARGET, squad = &self.config.squad; "Our tip({}) is too far off their new block({}) waiting for sync", our_tip, max_payload_height); + return Ok(MessageAcceptance::Accept); + } + info!(target: LOG_TARGET, squad = &self.config.squad; "We are missing less than 5 blocks, sending sync request with missing blocks to {}", propagation_source); + let sync_share_chain = SyncShareChain { + algo, + peer: propagation_source, + missing_parents, + is_from_new_block_notify: true, + }; + + let _ = self.inner_request_tx.send(InnerRequest::DoSyncChain(sync_share_chain)); } - info!(target: LOG_TARGET, squad = &self.config.squad; "We are missing less than 5 blocks, sending sync request with missing blocks to {}", propagation_source); - let sync_share_chain = SyncShareChain { - algo, - peer: propagation_source, - missing_parents, - is_from_new_block_notify: true, - }; - - let _ = self.inner_request_tx.send(InnerRequest::DoSyncChain(sync_share_chain)); }, Err(error) => { error!(target: LOG_TARGET, squad = &self.config.squad; "Failed to add synced blocks to share chain: {error:?}"); @@ -982,19 +980,18 @@ where S: ShareChain tokio::spawn(async move { match share_chain.add_synced_blocks(&blocks).await { Ok(new_tip) => { - info!(target: LOG_TARGET, squad; "[{:?}] Synced blocks added to share chain, new tip added [{}]",algo, new_tip); - info!(target: LOG_TARGET, squad; "[{:?}] blocks for the following heights where added : {:?}", algo, blocks.iter().map(|block| block.height.to_string()).collect::>()); - }, - Err(crate::sharechain::error::ShareChainError::BlockParentDoesNotExist { missing_parents }) => { - info!(target: LOG_TARGET, squad; "[{:?}] missing parents {:?}", algo, missing_parents.iter().map(|(height, hash)| format!("{}({:x}{:x}{:x}{:x})",height.to_string(), hash[0], hash[1], hash[2], hash[3])).collect::>()); - let sync_share_chain = SyncShareChain { - algo, - peer, - missing_parents, - is_from_new_block_notify: false, - }; + info!(target: LOG_TARGET, squad; "[{:?}] Synced blocks added to share chain: {}",algo, new_tip); + let missing_parents = new_tip.to_missing_parents_vec(); + if !missing_parents.is_empty() { + let sync_share_chain = SyncShareChain { + algo, + peer, + missing_parents, + is_from_new_block_notify: false, + }; - let _ = tx.send(InnerRequest::DoSyncChain(sync_share_chain)); + let _ = tx.send(InnerRequest::DoSyncChain(sync_share_chain)); + } }, Err(error) => { error!(target: LOG_TARGET, squad; "Failed to add synced blocks to share chain: {error:?}"); @@ -1469,41 +1466,32 @@ where S: ShareChain tokio::spawn(async move { blocks.sort_by(|a, b| a.height.cmp(&b.height)); let last_block_from_them = blocks.last().map(|b| (b.height, b.hash)); - let mut missing_blocks = HashSet::new(); - let mut new_tip = false; + let mut new_tip = ChainAddResult::default(); let mut blocks_added = Vec::new(); for b in &blocks { match share_chain.add_synced_blocks(&[b.clone()]).await { Ok(result) => { blocks_added.push(format!("{}({})", b.height, &b.hash.to_hex()[0..8])); - if result { - new_tip = result; - } + new_tip.combine(result); }, - Err(error) => match error { - crate::sharechain::error::ShareChainError::BlockParentDoesNotExist { missing_parents } => { - for (height, hash) in missing_parents { - missing_blocks.insert((height, hash)); - } - }, - _ => { - error!(target: SYNC_REQUEST_LOG_TARGET, squad; "Failed to add Catchup synced blocks to share chain: {error:?}"); - network_peer_store - .write() - .await - .move_to_grey_list(peer, format!("Block failed validation: {error}")); - return; - }, + Err(error) => { + error!(target: SYNC_REQUEST_LOG_TARGET, squad; "Failed to add Catchup synced blocks to share chain: {error:?}"); + network_peer_store + .write() + .await + .move_to_grey_list(peer, format!("Block failed validation: {error}")); + return; }, } } - info!(target: LOG_TARGET, "[{:?}][new tip: {}] Blocks added {:?}", new_tip, algo, blocks_added); - if missing_blocks.len() > 0 { - warn!(target: SYNC_REQUEST_LOG_TARGET, squad; "Catchup sync Reporting missing blocks({}): {:?}", missing_blocks.len(), missing_blocks.iter().map(|(height, hash)| format!("{}({:x}{:x}{:x}{:x})",height.to_string(), hash[0], hash[1], hash[2], hash[3])).collect::>()); + info!(target: LOG_TARGET, "[{:?}] Blocks via catchup sync added {:?}", algo, blocks_added); + info!(target: LOG_TARGET, "[{:?}] Blocks via catchup sync result {}", algo, new_tip); + let missing_parents = new_tip.to_missing_parents_vec(); + if missing_parents.len() > 0 { let sync_share_chain = SyncShareChain { algo, peer, - missing_parents: missing_blocks.into_iter().collect(), + missing_parents, is_from_new_block_notify: false, }; let _ = tx.send(InnerRequest::DoSyncChain(sync_share_chain)); @@ -1713,8 +1701,6 @@ where S: ShareChain } async fn attempt_relay_reservation(&mut self) { - dbg!("Attempt relay reservation"); - // Can happen that a previous lock already set the relaty if self.swarm.external_addresses().count() > 0 { warn!(target: LOG_TARGET, "No need to relay, we have an external address or relay already"); diff --git a/src/sharechain/error.rs b/src/sharechain/error.rs index dad355a..c3159e6 100644 --- a/src/sharechain/error.rs +++ b/src/sharechain/error.rs @@ -37,12 +37,14 @@ pub enum ShareChainError { BlockLevelNotFound, #[error("Validation error: {0}")] ValidationError(#[from] ValidationError), - #[error("Missing parents")] - BlockParentDoesNotExist { missing_parents: Vec<(u64, FixedHash)> }, #[error("Missing block validation params!")] MissingBlockValidationParams, #[error("Uncle block was in main chain. Height: {height}, Hash: {hash}")] UncleInMainChain { height: u64, hash: FixedHash }, + #[error("Uncle block does not link back to main chain")] + UncleParentNotInMainChain, + #[error("Block does not have correct total work accumulated")] + BlockTotalWorkMismatch, } #[derive(Error, Debug)] diff --git a/src/sharechain/in_memory.rs b/src/sharechain/in_memory.rs index baebcd1..478fa40 100644 --- a/src/sharechain/in_memory.rs +++ b/src/sharechain/in_memory.rs @@ -33,8 +33,8 @@ use crate::{ server::{http::stats_collector::StatsBroadcastClient, PROTOCOL_VERSION}, sharechain::{ error::{ShareChainError, ValidationError}, - p2block::P2Block, - p2chain::P2Chain, + p2block::{P2Block, P2BlockBuilder}, + p2chain::{ChainAddResult, P2Chain}, BlockValidationParams, ShareChain, }, @@ -175,7 +175,7 @@ impl InMemoryShareChain { block: Arc, params: Option>, syncing: bool, - ) -> Result { + ) -> Result { let new_block_p2pool_height = block.height; // Check if already added. @@ -185,7 +185,7 @@ impl InMemoryShareChain { info!(target: LOG_TARGET, "[{:?}] ✅ Block already added: {}:{}, verified: {}", self.pow_algo, block.height, &block.hash.to_hex()[0..8], block_in_chain.verified); - return Ok(false); + return Ok(ChainAddResult::default()); } } @@ -372,7 +372,7 @@ impl InMemoryShareChain { #[async_trait] impl ShareChain for InMemoryShareChain { - async fn submit_block(&self, block: Arc) -> Result { + async fn submit_block(&self, block: Arc) -> Result { if block.version != PROTOCOL_VERSION { return Err(ShareChainError::BlockValidation("Block version is too low".to_string())); } @@ -393,28 +393,19 @@ impl ShareChain for InMemoryShareChain { p2_chain_write_lock.get_max_chain_length() as u64, ); match &res { - Ok(false) => { - info!(target: LOG_TARGET, "[{:?}] ✅ added Block: {:?} successfully, no tip change", self.pow_algo, height) - }, - Ok(true) => { - info!(target: LOG_TARGET, "[{:?}] ✅ added Block: {:?} successfully, tip changed", self.pow_algo, height) - }, - Err(ShareChainError::BlockParentDoesNotExist { missing_parents }) => { - let missing_heights = missing_parents.iter().map(|data| data.0).collect::>(); - info!(target: LOG_TARGET, "[{:?}] Missing blocks for the following heights: {:?}", self.pow_algo, missing_heights); + Ok(tip) => { + info!(target: LOG_TARGET, "[{:?}] ✅ added Block({}): {} ", self.pow_algo, height, tip) }, Err(e) => warn!(target: LOG_TARGET, "Failed to add block from submit (height {}): {}", height, e), } res } - async fn add_synced_blocks(&self, blocks: &[Arc]) -> Result { + async fn add_synced_blocks(&self, blocks: &[Arc]) -> Result { let mut p2_chain_write_lock = self.p2_chain.write().await; - let mut new_tip = false; let mut blocks = blocks.to_vec(); let mut known_blocks_incoming = Vec::new(); - let mut missing_parents = HashMap::new(); if !blocks.is_sorted_by_key(|block| block.height) { blocks.sort_by(|a, b| a.height.cmp(&b.height)); // return Err(ShareChainError::BlockValidation("Blocks are not sorted by height".to_string())); @@ -422,6 +413,7 @@ impl ShareChain for InMemoryShareChain { for block in blocks.iter() { known_blocks_incoming.push(block.hash); } + let mut add_result = ChainAddResult::default(); 'outer: for block in blocks { if block.version != PROTOCOL_VERSION { @@ -439,33 +431,37 @@ impl ShareChain for InMemoryShareChain { .await { Ok(tip_change) => { - debug!(target: LOG_TARGET, "[{:?}] ✅ added Block: {:?} successfully. Tip changed: {}", self.pow_algo, height, tip_change); - if tip_change { - new_tip = true; + debug!(target: LOG_TARGET, "[{:?}] ✅ added Block({}): {} ", self.pow_algo, height, tip_change); + match (&mut add_result.new_tip, tip_change.new_tip) { + (Some(current_tip), Some(other_tip)) => { + if other_tip.1 > current_tip.1 { + add_result.new_tip = Some(other_tip); + } + }, + (None, Some(new_tip)) => { + add_result.new_tip = Some(new_tip); + }, + _ => {}, } - }, - Err(e) => { - if let ShareChainError::BlockParentDoesNotExist { - missing_parents: new_missing_parents, - } = e - { - for new_missing_parent in new_missing_parents { - if known_blocks_incoming.contains(&new_missing_parent.1) { + if !tip_change.missing_blocks.is_empty() { + for missing_block in tip_change.missing_blocks.iter() { + if known_blocks_incoming.contains(missing_block.0) { continue; } - missing_parents.insert(new_missing_parent.1, new_missing_parent.0); - if missing_parents.len() > MAX_MISSING_PARENTS { + add_result.missing_blocks.insert(*missing_block.0, *missing_block.1); + if add_result.missing_blocks.len() > MAX_MISSING_PARENTS { break 'outer; } } - } else { - warn!(target: LOG_TARGET, "Failed to add block during sync (height {}): {}", height, e); - return Err(e); } }, + Err(e) => { + warn!(target: LOG_TARGET, "Failed to add block during sync (height {}): {}", height, e); + return Err(e); + }, } } - if new_tip { + if add_result.new_tip.is_some() { let _ = self.stat_client.send_chain_changed( self.pow_algo, p2_chain_write_lock.get_height(), @@ -473,16 +469,10 @@ impl ShareChain for InMemoryShareChain { ); } - if !missing_parents.is_empty() { - info!(target: LOG_TARGET, "[{:?}] Missing blocks for the following heights: {:?}", self.pow_algo, missing_parents.iter().map(|(hash,height)| format!("{}({:x}{:x}{:x}{:x})",height.to_string(), hash[0], hash[1], hash[2], hash[3])).collect::>()); - return Err(ShareChainError::BlockParentDoesNotExist { - missing_parents: missing_parents - .into_iter() - .map(|(hash, height)| (height, hash)) - .collect(), - }); + if !add_result.missing_blocks.is_empty() { + info!(target: LOG_TARGET, "[{:?}] Missing blocks for the following heights: {:?}", self.pow_algo, add_result.missing_blocks.iter().map(|(hash,height)| format!("{}({:x}{:x}{:x}{:x})",height.to_string(), hash[0], hash[1], hash[2], hash[3])).collect::>()); } - Ok(new_tip) + Ok(add_result) } async fn tip_height(&self) -> Result { @@ -587,16 +577,14 @@ impl ShareChain for InMemoryShareChain { let chain_read_lock = self.p2_chain.read().await; // edge case for chain start - let (last_block_hash, new_height) = match chain_read_lock.get_tip() { - Some(tip) => { - let hash = match tip.block_in_main_chain() { - Some(block) => block.hash, - None => FixedHash::zero(), - }; - (hash, tip.height.saturating_add(1)) + let prev_block = match chain_read_lock.get_tip() { + Some(tip) => match tip.block_in_main_chain() { + Some(block) => block.clone(), + None => Arc::new(P2Block::default()), }, - None => (FixedHash::zero(), 0), + None => Arc::new(P2Block::default()), }; + let new_height = prev_block.height.saturating_add(1); // lets calculate the uncles // uncle rules are: @@ -605,8 +593,9 @@ impl ShareChain for InMemoryShareChain { // 3. The uncle must link back to the main chain // 4. The chain height must be above 5 let mut excluded_uncles: Vec = vec![]; - let mut uncles: Vec<(u64, FixedHash)> = vec![]; + let mut uncles: Vec> = vec![]; if new_height >= UNCLE_START_HEIGHT { + // gather potential uncles for height in new_height.saturating_sub(MAX_UNCLE_AGE)..new_height { if let Some(older_level) = chain_read_lock.level_at_height(height) { let chain_block = older_level @@ -614,57 +603,56 @@ impl ShareChain for InMemoryShareChain { .ok_or(ShareChainError::BlockNotFound)?; // Blocks in the main chain can't be uncles excluded_uncles.push(chain_block.hash); + // Blocks can only be an uncle once for uncle in &chain_block.uncles { excluded_uncles.push(uncle.1); } - for block in &older_level.blocks { - uncles.push((height, *block.0)); + for block in older_level.blocks.values() { + uncles.push(block.clone()); } } } + for uncle in &uncles { - if chain_read_lock.level_at_height(uncle.0).is_none() { - excluded_uncles.push((*uncle.1).into()); + if chain_read_lock.level_at_height(uncle.height).is_none() { + excluded_uncles.push((*uncle.hash).into()); continue; } - if let Some(level) = chain_read_lock.level_at_height(uncle.0) { - if let Some(uncle_block) = level.blocks.get(&uncle.1) { - let parent = match chain_read_lock.get_parent_block(uncle_block) { - Some(parent) => parent, - None => { - excluded_uncles.push(uncle.1); - continue; - }, - }; - if chain_read_lock - .level_at_height(parent.height) - .ok_or(ShareChainError::BlockLevelNotFound)? - .chain_block != - parent.hash - { - excluded_uncles.push(uncle.1); - } - } else { - excluded_uncles.push(uncle.1); - } + + // parent block needs to exist + let parent = match chain_read_lock.get_parent_block(&*uncle) { + Some(parent) => parent, + None => { + excluded_uncles.push(uncle.hash); + continue; + }, + }; + // parent needs to be in the current main chain + if chain_read_lock + .level_at_height(parent.height) + .ok_or(ShareChainError::BlockLevelNotFound)? + .chain_block != + parent.hash + { + excluded_uncles.push(uncle.hash); } } // Remove excluded. for excluded in &excluded_uncles { - uncles.retain(|uncle| &uncle.1 != excluded); + uncles.retain(|uncle| &uncle.hash != excluded); } + // limit remaining to uncle limit uncles.truncate(UNCLE_LIMIT); } - Ok(P2Block::builder() + Ok(P2BlockBuilder::new(Some(&prev_block)) .with_timestamp(EpochTime::now()) - .with_prev_hash(last_block_hash) .with_height(new_height) - .with_uncles(uncles) + .with_uncles(&uncles)? .with_miner_wallet_address(miner_address.clone()) .with_miner_coinbase_extra(coinbase_extra) - .build()) + .build()?) } async fn get_blocks(&self, requested_blocks: &[(u64, FixedHash)]) -> Vec> { @@ -812,7 +800,7 @@ impl ShareChain for InMemoryShareChain { #[cfg(test)] pub mod test { use tari_common::configuration::Network; - use tari_common_types::{tari_address::TariAddressFeatures, types::BlockHash}; + use tari_common_types::tari_address::TariAddressFeatures; use tari_crypto::{keys::PublicKey, ristretto::RistrettoPublicKey}; use super::*; @@ -855,22 +843,23 @@ pub mod test { .unwrap(); let mut timestamp = EpochTime::now(); - let mut prev_hash = BlockHash::zero(); + let mut prev_block = None; let static_coinbase_extra = Vec::new(); for i in 0..15 { let address = new_random_address(); timestamp = timestamp.checked_add(EpochTime::from(10)).unwrap(); - let block = P2Block::builder() + let block = P2BlockBuilder::new(prev_block.as_ref()) .with_timestamp(timestamp) .with_height(i) .with_miner_wallet_address(address.clone()) .with_target_difficulty(Difficulty::from_u64(1).unwrap()) - .with_prev_hash(prev_hash) + .unwrap() .with_miner_coinbase_extra(static_coinbase_extra.clone()) - .build(); + .build() + .unwrap(); - prev_hash = block.generate_hash(); + prev_block = Some((*block).clone()); share_chain.submit_block(block).await.unwrap(); } @@ -903,7 +892,7 @@ pub mod test { .unwrap(); let mut timestamp = EpochTime::now(); - let mut prev_hash = BlockHash::zero(); + let mut prev_block = None; let mut miners = Vec::new(); for _ in 0..5 { let address = new_random_address(); @@ -913,16 +902,17 @@ pub mod test { for i in 0..15 { let address = miners[i % 5].clone(); timestamp = timestamp.checked_add(EpochTime::from(10)).unwrap(); - let block = P2Block::builder() + let block = P2BlockBuilder::new(prev_block.as_ref()) .with_timestamp(timestamp) .with_height(i as u64) .with_miner_wallet_address(address.clone()) .with_target_difficulty(Difficulty::from_u64(1).unwrap()) - .with_prev_hash(prev_hash) + .unwrap() .with_miner_coinbase_extra(static_coinbase_extra.clone()) - .build(); + .build() + .unwrap(); - prev_hash = block.generate_hash(); + prev_block = Some((*block).clone()); share_chain.submit_block(block).await.unwrap(); } @@ -955,7 +945,7 @@ pub mod test { .unwrap(); let mut timestamp = EpochTime::now(); - let mut prev_hash = BlockHash::zero(); + let mut prev_block = None; let mut miners = Vec::new(); for _ in 0..5 { let address = new_random_address(); @@ -967,36 +957,41 @@ pub mod test { timestamp = timestamp.checked_add(EpochTime::from(10)).unwrap(); let mut uncles = Vec::new(); if i > 10 { - let prev_hash_uncle = share_chain + let prev_uncle = share_chain .p2_chain .read() .await .level_at_height(i as u64 - 2) .unwrap() - .chain_block; + .block_in_main_chain() + .unwrap() + .clone(); // lets create an uncle block - let block = P2Block::builder() + let block = P2BlockBuilder::new(Some(&prev_uncle)) .with_timestamp(timestamp) .with_height(i as u64 - 1) .with_miner_wallet_address(address.clone()) .with_target_difficulty(Difficulty::from_u64(1).unwrap()) - .with_prev_hash(prev_hash_uncle) + .unwrap() .with_miner_coinbase_extra(static_coinbase_extra.clone()) - .build(); - uncles.push((i as u64 - 1, block.hash)); + .build() + .unwrap(); + uncles.push(block.clone()); share_chain.submit_block(block).await.unwrap(); } - let block = P2Block::builder() + let block = P2BlockBuilder::new(prev_block.as_ref()) .with_timestamp(timestamp) .with_height(i as u64) .with_miner_wallet_address(address.clone()) .with_target_difficulty(Difficulty::from_u64(1).unwrap()) - .with_uncles(uncles) - .with_prev_hash(prev_hash) + .unwrap() + .with_uncles(&uncles) + .unwrap() .with_miner_coinbase_extra(static_coinbase_extra.clone()) - .build(); + .build() + .unwrap(); - prev_hash = block.generate_hash(); + prev_block = Some((*block).clone()); share_chain.submit_block(block).await.unwrap(); } @@ -1027,14 +1022,15 @@ pub mod test { async fn test_request_sync_starts_from_highest_match() { let chain = new_chain(); let mut blocks = Vec::new(); - let mut prev_hash = BlockHash::zero(); + let mut prev_block = None; for i in 0..10 { - let block = P2Block::builder() + let block = P2BlockBuilder::new(prev_block.as_ref()) .with_height(i) - .with_prev_hash(prev_hash) .with_target_difficulty(Difficulty::from_u64(1).unwrap()) - .build(); - prev_hash = block.generate_hash(); + .unwrap() + .build() + .unwrap(); + prev_block = Some((*block).clone()); blocks.push(block); } chain.add_synced_blocks(&blocks).await.unwrap(); @@ -1072,12 +1068,12 @@ pub mod test { assert_eq!(heights, vec![8, 9]); // Add an extra block in their blocks - let missing_block = P2Block::builder() + let missing_block = P2BlockBuilder::new(prev_block.as_ref()) .with_height(11) - .with_prev_hash(prev_hash) .with_target_difficulty(Difficulty::from_u64(10).unwrap()) - .build(); - // prev_hash = block.generate_hash(); + .unwrap() + .build() + .unwrap(); their_blocks.push((11, missing_block.hash)); let res = chain diff --git a/src/sharechain/mod.rs b/src/sharechain/mod.rs index 9c78efc..2cebd5e 100644 --- a/src/sharechain/mod.rs +++ b/src/sharechain/mod.rs @@ -30,7 +30,7 @@ use tari_core::{ proof_of_work::{randomx_factory::RandomXFactory, AccumulatedDifficulty, Difficulty}, }; -use crate::sharechain::{error::ShareChainError, p2block::P2Block}; +use crate::sharechain::{error::ShareChainError, p2block::P2Block, p2chain::ChainAddResult}; /// Chain ID is an identifier which makes sure we apply the same rules to blocks. /// Note: This must be updated when new logic applied to blocks handling. @@ -95,10 +95,10 @@ impl BlockValidationParams { pub(crate) trait ShareChain: Send + Sync + 'static { async fn get_total_chain_pow(&self) -> AccumulatedDifficulty; /// Adds a new block if valid to chain. - async fn submit_block(&self, block: Arc) -> Result; + async fn submit_block(&self, block: Arc) -> Result; /// Add multiple blocks at once. - async fn add_synced_blocks(&self, blocks: &[Arc]) -> Result; + async fn add_synced_blocks(&self, blocks: &[Arc]) -> Result; /// Returns the tip of height in chain (from original Tari block header) async fn tip_height(&self) -> Result; diff --git a/src/sharechain/p2block.rs b/src/sharechain/p2block.rs index ef627e1..3596847 100644 --- a/src/sharechain/p2block.rs +++ b/src/sharechain/p2block.rs @@ -15,7 +15,7 @@ use tari_common_types::{ use tari_core::{ blocks::{genesis_block::get_genesis_block, Block, BlockHeader, BlocksHashDomain}, consensus::DomainSeparatedConsensusHasher, - proof_of_work::Difficulty, + proof_of_work::{AccumulatedDifficulty, Difficulty}, transactions::transaction_components::TransactionOutput, }; use tari_script::script; @@ -55,14 +55,33 @@ pub(crate) struct P2Block { pub uncles: Vec<(u64, BlockHash)>, pub miner_coinbase_extra: Vec, pub verified: bool, + pub total_pow: AccumulatedDifficulty, } -impl_conversions!(P2Block); -impl P2Block { - pub fn builder() -> BlockBuilder { - BlockBuilder::new() +impl Default for P2Block { + fn default() -> Self { + Self { + version: PROTOCOL_VERSION, + hash: Default::default(), + timestamp: EpochTime::now(), + prev_hash: Default::default(), + height: 0, + original_header: BlockHeader::new(0), + coinbases: Vec::new(), + other_output_hash: Default::default(), + miner_wallet_address: Default::default(), + sent_to_main_chain: false, + target_difficulty: Difficulty::min(), + uncles: Vec::new(), + miner_coinbase_extra: vec![], + verified: false, + total_pow: AccumulatedDifficulty::default(), + } } +} +impl_conversions!(P2Block); +impl P2Block { pub fn generate_hash(&self) -> BlockHash { DomainSeparatedConsensusHasher::>::new("block") .chain(&self.prev_hash) @@ -74,6 +93,7 @@ impl P2Block { .chain(&self.target_difficulty) .chain(&self.uncles) .chain(&self.miner_coinbase_extra) + .chain(&self.total_pow.as_u128()) .finalize() .into() } @@ -106,31 +126,29 @@ impl P2Block { } } -pub(crate) struct BlockBuilder { +pub struct P2BlockBuilder { block: P2Block, use_specific_hash: bool, + added_target_difficulty: bool, } -impl BlockBuilder { - pub fn new() -> Self { +impl P2BlockBuilder { + pub fn new(prev_block: Option<&P2Block>) -> Self { + let mut block = P2Block::default(); + match prev_block { + Some(prev_block) => { + block.prev_hash = prev_block.hash; + block.total_pow = prev_block.total_pow.clone(); + }, + None => { + block.prev_hash = BlockHash::zero(); + block.total_pow = AccumulatedDifficulty::default(); + }, + } Self { use_specific_hash: false, - block: P2Block { - version: PROTOCOL_VERSION, - hash: Default::default(), - timestamp: EpochTime::now(), - prev_hash: Default::default(), - height: 0, - original_header: BlockHeader::new(0), - coinbases: Vec::new(), - other_output_hash: Default::default(), - miner_wallet_address: Default::default(), - sent_to_main_chain: false, - target_difficulty: Difficulty::min(), - uncles: Vec::new(), - miner_coinbase_extra: vec![], - verified: false, - }, + added_target_difficulty: false, + block, } } @@ -139,19 +157,20 @@ impl BlockBuilder { self } - pub fn with_prev_hash(mut self, prev_hash: BlockHash) -> Self { - self.block.prev_hash = prev_hash; - self - } - pub fn with_height(mut self, height: u64) -> Self { self.block.height = height; self } - pub fn with_target_difficulty(mut self, target_difficulty: Difficulty) -> Self { + pub fn with_target_difficulty(mut self, target_difficulty: Difficulty) -> Result { + self.added_target_difficulty = true; self.block.target_difficulty = target_difficulty; - self + self.block.total_pow = self + .block + .total_pow + .checked_add_difficulty(target_difficulty) + .ok_or(ShareChainError::DifficultyOverflow)?; + Ok(self) } pub fn with_tari_block(mut self, block: Block) -> Result { @@ -169,15 +188,36 @@ impl BlockBuilder { self } - pub fn with_uncles(mut self, uncles: Vec<(u64, BlockHash)>) -> Self { - self.block.uncles = uncles; - self + pub fn with_uncles(mut self, uncles: &Vec>) -> Result { + let mut block_uncles = Vec::new(); + for uncle in uncles { + block_uncles.push((uncle.height, uncle.hash)); + self.block.total_pow = self + .block + .total_pow + .checked_add_difficulty(uncle.target_difficulty) + .ok_or(ShareChainError::DifficultyOverflow)?; + } + self.block.uncles = block_uncles; + Ok(self) } - pub fn build(mut self) -> Arc { + pub fn build(mut self) -> Result, ShareChainError> { + if !self.added_target_difficulty || self.block.prev_hash == BlockHash::zero() { + if self.block.prev_hash == BlockHash::zero() { + self.block.total_pow = AccumulatedDifficulty::from_u128(self.block.target_difficulty.as_u64() as u128) + .map_err(|_| ShareChainError::DifficultyOverflow)?; + } else { + self.block.total_pow = self + .block + .total_pow + .checked_add_difficulty(self.block.target_difficulty) + .ok_or(ShareChainError::DifficultyOverflow)?; + } + } if !self.use_specific_hash { self.block.hash = self.block.generate_hash(); } - Arc::new(self.block) + Ok(Arc::new(self.block)) } } diff --git a/src/sharechain/p2chain.rs b/src/sharechain/p2chain.rs index 8de3091..b235723 100644 --- a/src/sharechain/p2chain.rs +++ b/src/sharechain/p2chain.rs @@ -22,22 +22,26 @@ use std::{ collections::{HashMap, VecDeque}, + fmt, + fmt::{Display, Formatter}, ops::Deref, sync::Arc, }; use log::{debug, error, info}; use tari_common_types::types::FixedHash; -use tari_core::proof_of_work::{lwma_diff::LinearWeightedMovingAverage, AccumulatedDifficulty, Difficulty}; +use tari_core::proof_of_work::{lwma_diff::LinearWeightedMovingAverage, AccumulatedDifficulty}; use tari_utilities::hex::Hex; use crate::sharechain::{ error::ShareChainError, + in_memory::MAX_UNCLE_AGE, p2block::P2Block, p2chain_level::P2ChainLevel, BLOCK_TARGET_TIME, DIFFICULTY_ADJUSTMENT_WINDOW, }; + const LOG_TARGET: &str = "tari::p2pool::sharechain::chain"; // this is the max we are allowed to go over the size pub const SAFETY_MARGIN: usize = 20; @@ -48,12 +52,93 @@ pub const MAX_SYNC_STORE: usize = 200; // this is the max missing parents we allow to process before we stop processing a chain and wait for more parents pub const MAX_MISSING_PARENTS: usize = 100; +#[derive(Debug, Clone)] +pub struct ChainAddResult { + pub new_tip: Option<(FixedHash, u64)>, + pub missing_blocks: HashMap, +} + +impl ChainAddResult { + pub fn combine(&mut self, other: ChainAddResult) { + match (&self.new_tip, other.new_tip) { + (Some(current_tip), Some(other_tip)) => { + if other_tip.1 > current_tip.1 { + self.new_tip = Some(other_tip); + } + }, + (None, Some(new_tip)) => { + self.new_tip = Some(new_tip); + }, + _ => {}, + } + for (hash, height) in other.missing_blocks { + if self.missing_blocks.len() >= MAX_MISSING_PARENTS { + break; + } + self.missing_blocks.insert(hash, height); + } + } + + pub fn set_new_tip(&mut self, hash: FixedHash, height: u64) { + match self.new_tip { + Some((_, current_height)) => { + if height > current_height { + self.new_tip = Some((hash, height)); + } + }, + None => { + self.new_tip = Some((hash, height)); + }, + }; + } + + pub fn to_missing_parents_vec(self) -> Vec<(u64, FixedHash)> { + self.missing_blocks + .into_iter() + .map(|(hash, height)| (height, hash)) + .collect() + } +} + +impl Default for ChainAddResult { + fn default() -> Self { + Self { + new_tip: None, + missing_blocks: HashMap::new(), + } + } +} + +impl Display for ChainAddResult { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { + if let Some(tip) = self.new_tip { + writeln!( + f, + "Added new tip {}({:x}{:x}{:x}{:x})", + tip.1, tip.0[0], tip.0[1], tip.0[2], tip.0[3] + )?; + } else { + writeln!(f, "No new tip added")?; + } + if !self.missing_blocks.is_empty() { + let mut missing_blocks: Vec = Vec::new(); + for (hash, height) in &self.missing_blocks { + missing_blocks.push(format!( + "{}({:x}{:x}{:x}{:x})", + height, hash[0], hash[1], hash[2], hash[3] + )); + } + writeln!(f, "Missing blocks: {:?}", missing_blocks)?; + } + Ok(()) + } +} + pub struct P2Chain { pub cached_shares: Option)>>, pub(crate) levels: VecDeque, total_size: usize, share_window: usize, - total_accumulated_tip_difficulty: AccumulatedDifficulty, current_tip: u64, pub lwma: LinearWeightedMovingAverage, sync_store: HashMap>, @@ -62,7 +147,13 @@ pub struct P2Chain { impl P2Chain { pub fn total_accumulated_tip_difficulty(&self) -> AccumulatedDifficulty { - self.total_accumulated_tip_difficulty + match self.get_tip() { + Some(tip) => tip + .block_in_main_chain() + .map(|block| block.total_pow) + .unwrap_or(AccumulatedDifficulty::min()), + None => AccumulatedDifficulty::min(), + } } pub fn level_at_height(&self, height: u64) -> Option<&P2ChainLevel> { @@ -80,6 +171,11 @@ impl P2Chain { level.blocks.get(hash) } + fn get_chain_block_at_height(&self, height: u64) -> Option<&Arc> { + let level = self.level_at_height(height)?; + level.blocks.get(&level.chain_block) + } + pub fn level_at_height_mut(&mut self, height: u64) -> Option<&mut P2ChainLevel> { let tip = self.levels.front()?.height; if height > tip { @@ -90,14 +186,6 @@ impl P2Chain { .get_mut(usize::try_from(index?).expect("32 bit systems not supported")) } - fn decrease_total_chain_difficulty(&mut self, difficulty: Difficulty) -> Result<(), ShareChainError> { - self.total_accumulated_tip_difficulty = self - .total_accumulated_tip_difficulty - .checked_sub_difficulty(difficulty) - .ok_or(ShareChainError::DifficultyOverflow)?; - Ok(()) - } - pub fn new_empty(total_size: usize, share_window: usize) -> Self { let levels = VecDeque::with_capacity(total_size + 1); let lwma = LinearWeightedMovingAverage::new(DIFFICULTY_ADJUSTMENT_WINDOW, BLOCK_TARGET_TIME) @@ -107,7 +195,6 @@ impl P2Chain { levels, total_size, share_window, - total_accumulated_tip_difficulty: AccumulatedDifficulty::min(), current_tip: 0, lwma, sync_store: HashMap::new(), @@ -136,76 +223,25 @@ impl P2Chain { // if the tip is none and we added a block at height 0, it might return it here as a tip, so we need to check if // the newly added block == 0 self.lwma.add_back(block.timestamp, block.target_difficulty); - if self.get_tip().is_none() || (self.get_tip().map(|tip| tip.height).unwrap_or(0) == 0 && new_height == 0) { - self.total_accumulated_tip_difficulty = - AccumulatedDifficulty::from_u128(u128::from(block.target_difficulty.as_u64())) - .expect("Difficulty will always fit into accumulated difficulty"); - } else { - self.total_accumulated_tip_difficulty = self - .total_accumulated_tip_difficulty - .checked_add_difficulty(block.target_difficulty) - .ok_or(ShareChainError::DifficultyOverflow)?; - for uncle in &block.uncles { - if let Some(uncle_block) = self.get_block_at_height(uncle.0, &uncle.1) { - self.total_accumulated_tip_difficulty = self - .total_accumulated_tip_difficulty - .checked_add_difficulty(uncle_block.target_difficulty) - .ok_or(ShareChainError::DifficultyOverflow)?; - } - } - } let level = self .level_at_height_mut(new_height) .ok_or(ShareChainError::BlockLevelNotFound)?; level.chain_block = hash; self.current_tip = level.height; - // lets see if we need to subtract difficulty now that we have added a block - if self.current_tip >= self.share_window as u64 { - // our tip is more than the share window so its possible that we need to drop a block out of the pow window - if let Some(level) = self - .level_at_height(self.current_tip.saturating_sub(self.share_window as u64)) - .cloned() - { - let block = level.block_in_main_chain().ok_or(ShareChainError::BlockNotFound)?; - self.decrease_total_chain_difficulty(block.target_difficulty)?; - for (height, block_hash) in &block.uncles { - if let Some(link_level) = self.level_at_height(*height) { - let uncle_block = link_level - .blocks - .get(block_hash) - .ok_or(ShareChainError::BlockNotFound)?; - self.decrease_total_chain_difficulty(uncle_block.target_difficulty)?; - } - } - } - } Ok(()) } - fn verify_chain(&mut self, new_block_height: u64, hash: FixedHash) -> Result { + fn verify_chain(&mut self, new_block_height: u64, hash: FixedHash) -> Result { let mut next_level = VecDeque::new(); next_level.push_back((new_block_height, hash)); - let mut missing_parents = HashMap::new(); - let mut new_tip = false; + let mut new_tip = ChainAddResult::default(); while let Some((next_height, next_hash)) = next_level.pop_front() { match self.verify_chain_inner(next_height, next_hash) { - Ok((tip_change, new_missing_parents, do_next_level)) => { - if tip_change { - new_tip = true; - } - for new_missing_parent in new_missing_parents { - if missing_parents.len() < MAX_MISSING_PARENTS { - missing_parents.insert(new_missing_parent.1, new_missing_parent.0); - } - } - if missing_parents.len() >= MAX_MISSING_PARENTS { - return Err(ShareChainError::BlockParentDoesNotExist { - missing_parents: missing_parents - .into_iter() - .map(|(hash, height)| (height, hash)) - .collect(), - }); + Ok((add_result, do_next_level)) => { + new_tip.combine(add_result); + if new_tip.missing_blocks.len() >= MAX_MISSING_PARENTS { + return Ok(new_tip); } for item in do_next_level { if next_level.contains(&item) { @@ -221,15 +257,6 @@ impl P2Chain { Err(e) => return Err(e), } } - if !missing_parents.is_empty() { - return Err(ShareChainError::BlockParentDoesNotExist { - missing_parents: missing_parents - .into_iter() - .map(|(hash, height)| (height, hash)) - .collect(), - }); - } - Ok(new_tip) } @@ -238,10 +265,9 @@ impl P2Chain { &mut self, new_block_height: u64, hash: FixedHash, - ) -> Result<(bool, Vec<(u64, FixedHash)>, Vec<(u64, FixedHash)>), ShareChainError> { + ) -> Result<(ChainAddResult, Vec<(u64, FixedHash)>), ShareChainError> { // we should validate what we can if a block is invalid, we should delete it. - let mut new_tip = false; - let mut missing_parents = Vec::new(); + let mut new_tip = ChainAddResult::default(); let block = self .get_block_at_height(new_block_height, &hash) .ok_or(ShareChainError::BlockNotFound)? @@ -250,7 +276,7 @@ impl P2Chain { // do we know of the parent // we should not check the chain start for parents - if block.prev_hash != FixedHash::zero() && block.height != 0 { + if block.height != 0 { let mut is_parent_missing = false; let mut is_parent_in_main_chain = false; if self @@ -259,7 +285,9 @@ impl P2Chain { { is_parent_missing = true; // we dont know the parent - missing_parents.push((new_block_height.saturating_sub(1), block.prev_hash)); + new_tip + .missing_blocks + .insert(block.prev_hash, new_block_height.saturating_sub(1)); } else { is_parent_in_main_chain = self .level_at_height(new_block_height.saturating_sub(1)) @@ -267,279 +295,286 @@ impl P2Chain { .unwrap_or(false); } // now lets check the uncles - for uncle in block.uncles.iter() { - if self.get_block_at_height(uncle.0, &uncle.1).is_some() { - // Uncle cannot be in the main chain if the parent is in the main chain - if !is_parent_missing && is_parent_in_main_chain { - if let Some(level) = self.level_at_height(uncle.0) { - if level.chain_block == uncle.1 { - return Err(ShareChainError::UncleInMainChain { - height: uncle.0, - hash: uncle.1, - }); - } - } + for uncle in &block.uncles { + if let Some(uncle_block) = self.get_block_at_height(uncle.0, &uncle.1) { + if self.get_parent_block(&uncle_block).is_none() { + new_tip + .missing_blocks + .insert(uncle_block.prev_hash, uncle_block.height.saturating_sub(1)); } } else { - missing_parents.push((uncle.0, uncle.1)); + new_tip.missing_blocks.insert(uncle.1, uncle.0); } } } - // if !missing_parents.is_empty() { - // return Err(ShareChainError::BlockParentDoesNotExist { missing_parents }); - // } - if missing_parents.is_empty() { - // lets mark as verified - let level = self - .level_at_height(new_block_height) - .ok_or(ShareChainError::BlockLevelNotFound)?; - let block = level.blocks.get(&hash).ok_or(ShareChainError::BlockNotFound)?; - // 1. Block can only be verified once - // 2. Block is verified it if is the last block in the chain AND we are full. - // 3. otherwise, block can only be verified it's parents are verified and if it's uncles are verified - - if !block.verified { - let verified = true; - // TODO: implement a block becomes verified if it is the last block in the chain - - // if self.is_share_window_full() && self.last_share_window_block() == hash { - // // we are full and this is the last block in the chain - // verified = true; - // } else { - // we are not full or this is not the last block in the chain - - // TODO: implement verified only if parents are verified - // verified = true; - // if block.height != 0 { - // // we are not the first block - // if let Some(parent) = self.get_parent_block(block) { - // if !parent.verified { - // verified = false; - // } - // } - // } - // for uncle in block.uncles.iter() { - // if let Some(uncle_block) = self.get_block_at_height(uncle.0, &uncle.1) { - // if !uncle_block.verified { - // verified = false; - // } - // } - // } - // } - let mut block = block.deref().clone(); - // lets replace this - block.verified = verified; - let level = self - .level_at_height_mut(new_block_height) - .ok_or(ShareChainError::BlockLevelNotFound)?; - level.blocks.insert(hash, Arc::new(block)); - } + // lets verify the block + if !new_tip.missing_blocks.is_empty() { + let next_level_data = self.calculate_next_level_data(new_block_height, hash); + return Ok((new_tip, next_level_data)); } + self.verify_block(hash, new_block_height)?; + // we have to reload the block to check if verified is set to true now + let block = self + .get_block_at_height(new_block_height, &hash) + .ok_or(ShareChainError::BlockNotFound)? + .clone(); - // edge case for first block - // if the tip is none and we added a block at height 0, it might return it here as a tip, so we need to check if - // the newly added block == 0 + // edge case for chain start if self.get_tip().is_none() && new_block_height == 0 { self.set_new_tip(new_block_height, hash)?; - return Ok((true, missing_parents, Vec::new())); + new_tip.set_new_tip(hash, new_block_height); + return Ok((new_tip, Vec::new())); + } + if !block.verified { + return Ok((new_tip, Vec::new())); } - // is this block part of the main chain? - let new_block = self - .get_block_at_height(new_block_height, &hash) - .ok_or(ShareChainError::BlockNotFound)? - .clone(); + if self.get_tip().is_some() && self.get_tip().unwrap().chain_block == block.prev_hash { + // easy this builds on the tip + info!(target: LOG_TARGET, "[{:?}] New block added to tip, and is now the new tip: {:?}:{}", algo, new_block_height, &block.hash.to_hex()[0..8]); + for uncle in &block.uncles { + let uncle_block = self + .get_block_at_height(uncle.0, &uncle.1) + .ok_or(ShareChainError::BlockNotFound)?; + let uncle_parent = self + .get_parent_block(&uncle_block) + .ok_or(ShareChainError::BlockNotFound)?; + let uncle_level = self + .level_at_height(uncle.0.saturating_sub(1)) + .ok_or(ShareChainError::BlockLevelNotFound)?; + if uncle_level.chain_block != uncle_parent.hash { + return Err(ShareChainError::UncleParentNotInMainChain); + } + let own_level = self + .level_at_height(uncle.0) + .ok_or(ShareChainError::BlockLevelNotFound)?; + if own_level.chain_block == uncle.1 { + return Err(ShareChainError::UncleInMainChain { + height: uncle.0, + hash: uncle.1, + }); + } + } - if new_block.verified { - if self.get_tip().is_some() && self.get_tip().unwrap().chain_block == new_block.prev_hash { - // easy this builds on the tip - info!(target: LOG_TARGET, "[{:?}] New block added to tip, and is now the new tip: {:?}:{}", algo, new_block_height, &new_block.hash.to_hex()[0..8]); - self.set_new_tip(new_block_height, hash)?; - new_tip = true; - } else { - let mut all_blocks_verified = true; - debug!(target: LOG_TARGET, "[{:?}] New block is not on the tip, checking for reorg: {:?}", algo, new_block_height); - - let mut total_work = AccumulatedDifficulty::from_u128(new_block.target_difficulty.as_u64() as u128) - .expect("Difficulty will always fit into accumulated difficulty"); - - for uncle in new_block.uncles.iter() { - if let Some(uncle_block) = self.get_block_at_height(uncle.0, &uncle.1) { - if !uncle_block.verified { - // we cannot count unverified blocks - all_blocks_verified = false; - break; - } - total_work = total_work - .checked_add_difficulty(uncle_block.target_difficulty) - .ok_or(ShareChainError::DifficultyOverflow)?; - } else { - missing_parents.push((uncle.0, uncle.1)); - } + self.set_new_tip(new_block_height, hash)?; + new_tip.set_new_tip(hash, new_block_height); + } else { + let mut all_blocks_verified = true; + debug!(target: LOG_TARGET, "[{:?}] New block is not on the tip, checking for reorg: {:?}", algo, new_block_height); + + let mut current_counting_block = block.clone(); + let mut counter = 1; + // lets search for either the beginning of the chain, the fork or 2160 block back + loop { + if current_counting_block.height == 0 { + break; } - let mut current_counting_block = new_block.clone(); - let mut counter = 1; - while let Some(parent) = self.get_parent_block(¤t_counting_block) { + if let Some(parent) = self.get_parent_block(¤t_counting_block) { if !parent.verified { all_blocks_verified = false; - // we cannot count unverified blocks - break; - } - total_work = total_work - .checked_add_difficulty(parent.target_difficulty) - .ok_or(ShareChainError::DifficultyOverflow)?; - current_counting_block = parent.clone(); - for uncle in parent.uncles.iter() { - if let Some(uncle_block) = self.get_block_at_height(uncle.0, &uncle.1) { - total_work = total_work - .checked_add_difficulty(uncle_block.target_difficulty) - .ok_or(ShareChainError::DifficultyOverflow)?; - if !uncle_block.verified { - all_blocks_verified = false; - // we cannot count unverified blocks - break; + // so this block is unverified, we cannot count it but lets see if it just misses some blocks so + // we can ask for them + if self.get_parent_block(&parent).is_none() { + new_tip + .missing_blocks + .insert(parent.prev_hash, parent.height.saturating_sub(1)); + } + for uncle in &parent.uncles { + if self.get_block_at_height(uncle.0, &uncle.1).is_none() { + new_tip.missing_blocks.insert(uncle.1, uncle.0); } - } else { - missing_parents.push((uncle.0, uncle.1)); } - } - if !missing_parents.is_empty() { - break; - } - counter += 1; - if counter >= self.share_window { + // we cannot count unverified blocks break; } - if parent.height == 0 { - // we cant count further next block will be non existing as we have the first block here no - // lets change the counter to reflect max share window as this will be the max share window we - // can have - counter = self.share_window; - } + } else { + new_tip.missing_blocks.insert( + current_counting_block.prev_hash, + current_counting_block.height.saturating_sub(1), + ); + break; + }; + counter += 1; + if counter >= self.share_window { + break; } - if total_work > self.total_accumulated_tip_difficulty && - counter >= self.share_window && - all_blocks_verified && - missing_parents.is_empty() - { - new_tip = true; - // we need to reorg the chain - // lets start by resetting the lwma - self.lwma = LinearWeightedMovingAverage::new(DIFFICULTY_ADJUSTMENT_WINDOW, BLOCK_TARGET_TIME) - .expect("Failed to create LWMA"); - self.lwma.add_front(new_block.timestamp, new_block.target_difficulty); - let chain_height = self - .level_at_height_mut(new_block.height) - .ok_or(ShareChainError::BlockLevelNotFound)?; - chain_height.chain_block = new_block.hash; - self.cached_shares = None; - self.current_tip = new_block.height; - // lets fix the chain - // lets first go up and reset all chain block links - let mut current_height = new_block.height; - while self.level_at_height(current_height.saturating_add(1)).is_some() { - let mut_child_level = self.level_at_height_mut(current_height.saturating_add(1)).unwrap(); - mut_child_level.chain_block = FixedHash::zero(); - current_height += 1; - } - let mut current_block = new_block; - while self.level_at_height(current_block.height.saturating_sub(1)).is_some() { - let parent_level = - (self.level_at_height(current_block.height.saturating_sub(1)).unwrap()).clone(); - if current_block.prev_hash != parent_level.chain_block { - // safety check - let nextblock = parent_level.blocks.get(¤t_block.prev_hash); - if nextblock.is_none() { - error!(target: LOG_TARGET, "FATAL: Reorging (block in chain) failed because parent block was not found and chain data is corrupted."); - panic!( - "FATAL: Reorging (block in chain) failed because parent block was not found and \ - chain data is corrupted." - ); - } - // fix the main chain - let mut_parent_level = self - .level_at_height_mut(current_block.height.saturating_sub(1)) - .unwrap(); - mut_parent_level.chain_block = current_block.prev_hash; - current_block = nextblock.unwrap().clone(); - self.lwma - .add_front(current_block.timestamp, current_block.target_difficulty); - } else if !self.lwma.is_full() { - // we still need more blocks to fill up the lwma - let nextblock = parent_level.blocks.get(¤t_block.prev_hash); - if nextblock.is_none() { - error!(target: LOG_TARGET, "FATAL: Reorging (block not in chain) failed because parent block was not found and chain data is corrupted."); - panic!( - "FATAL: Reorging (block not in chain) failed because parent block was not found \ - and chain data is corrupted." - ); - } + let level = self + .level_at_height(current_counting_block.height) + .ok_or(ShareChainError::BlockLevelNotFound)?; + if level.chain_block == current_counting_block.hash { + break; + } + // we can unwrap as we now the parent exists + current_counting_block = self.get_parent_block(¤t_counting_block).unwrap().clone(); + } + if !all_blocks_verified { + let next_level_data = self.calculate_next_level_data(new_block_height, hash); + return Ok((new_tip, next_level_data)); + } + if !new_tip.missing_blocks.is_empty() { + // we are missing blocks, stop counting + let next_level_data = self.calculate_next_level_data(new_block_height, hash); + return Ok((new_tip, next_level_data)); + } + if block.total_pow > self.total_accumulated_tip_difficulty() { + new_tip.set_new_tip(hash, new_block_height); + // we need to reorg the chain + // lets start by resetting the lwma + self.lwma = LinearWeightedMovingAverage::new(DIFFICULTY_ADJUSTMENT_WINDOW, BLOCK_TARGET_TIME) + .expect("Failed to create LWMA"); + self.lwma.add_front(block.timestamp, block.target_difficulty); + let chain_height = self + .level_at_height_mut(block.height) + .ok_or(ShareChainError::BlockLevelNotFound)?; + chain_height.chain_block = block.hash; + self.cached_shares = None; + self.current_tip = block.height; + // lets fix the chain + // lets first go up and reset all chain block links + let mut current_height = block.height; + while self.level_at_height(current_height.saturating_add(1)).is_some() { + let mut_child_level = self.level_at_height_mut(current_height.saturating_add(1)).unwrap(); + mut_child_level.chain_block = FixedHash::zero(); + current_height += 1; + } + let mut current_block = block; + while self.level_at_height(current_block.height.saturating_sub(1)).is_some() { + let parent_level = (self.level_at_height(current_block.height.saturating_sub(1)).unwrap()).clone(); + if current_block.prev_hash != parent_level.chain_block { + // safety check + let nextblock = parent_level.blocks.get(¤t_block.prev_hash); + if nextblock.is_none() { + error!(target: LOG_TARGET, "FATAL: Reorging (block in chain) failed because parent block was not found and chain data is corrupted."); + panic!( + "FATAL: Reorging (block in chain) failed because parent block was not found and chain \ + data is corrupted." + ); + } + // fix the main chain + let mut_parent_level = self + .level_at_height_mut(current_block.height.saturating_sub(1)) + .unwrap(); + mut_parent_level.chain_block = current_block.prev_hash; + current_block = nextblock.unwrap().clone(); + self.lwma + .add_front(current_block.timestamp, current_block.target_difficulty); + } else if !self.lwma.is_full() { + // we still need more blocks to fill up the lwma + let nextblock = parent_level.blocks.get(¤t_block.prev_hash); + if nextblock.is_none() { + error!(target: LOG_TARGET, "FATAL: Reorging (block not in chain) failed because parent block was not found and chain data is corrupted."); + panic!( + "FATAL: Reorging (block not in chain) failed because parent block was not found and \ + chain data is corrupted." + ); + } - current_block = nextblock.unwrap().clone(); + current_block = nextblock.unwrap().clone(); - self.lwma - .add_front(current_block.timestamp, current_block.target_difficulty); - } else { - break; - } + self.lwma + .add_front(current_block.timestamp, current_block.target_difficulty); + } else { + break; + } - if current_block.height == 0 { - // edge case if there is less than the lwa size or share window in chain - break; - } + if current_block.height == 0 { + // edge case if there is less than the lwa size or share window in chain + break; } - self.total_accumulated_tip_difficulty = total_work; } } } - let mut next_level_data = Vec::new(); + let next_level_data = self.calculate_next_level_data(new_block_height, hash); - // let see if we already have a block that builds on top of this - let mut checking_height = new_block_height + 1; - while let Some(next_level) = self.level_at_height(checking_height) { - // we have a height here, lets check the blocks - let mut found_child = false; - for block in next_level.blocks.iter() { - if block.1.prev_hash == hash { - next_level_data.push((next_level.height, *block.0)); - found_child = true; - break; - } - } - // if we did not find a next parent to check, then we can break - if !found_child { - break; - } - checking_height += 1; + if !next_level_data.is_empty() { + debug!(target: LOG_TARGET, "[{:?}] Found link in chain with other blocks we have: {:?}", algo, new_block_height); } + Ok((new_tip, next_level_data)) + } + + fn calculate_next_level_data(&self, height: u64, hash: FixedHash) -> Vec<(u64, FixedHash)> { + let mut next_level_data = Vec::new(); - // let search if this block is an uncle of some higher block - for i in new_block_height + 1..new_block_height + 4 { - if let Some(level) = self.level_at_height(i) { - for higher_block in level.blocks.iter() { - for uncles in higher_block.1.uncles.iter() { + // let see if we already have a block is a missing block of some other block + for height in height..height + MAX_UNCLE_AGE { + if let Some(level) = self.level_at_height(height) { + for block in level.blocks.iter() { + for uncles in block.1.uncles.iter() { if uncles.1 == hash { - next_level_data.push((higher_block.1.height, higher_block.1.hash)); + next_level_data.push((block.1.height, block.1.hash)); } } + if block.1.prev_hash == hash { + next_level_data.push((block.1.height, block.1.hash)); + } } } } + next_level_data + } - if !next_level_data.is_empty() { - debug!(target: LOG_TARGET, "[{:?}] Found link in chain with other blocks we have: {:?}", algo, new_block_height); - // we have a parent here - return Ok((new_tip, missing_parents, next_level_data)); + // this assumes it has no missing parents + fn verify_block(&mut self, hash: FixedHash, height: u64) -> Result<(), ShareChainError> { + let level = self + .level_at_height(height) + .ok_or(ShareChainError::BlockLevelNotFound)?; + let block = level.blocks.get(&hash).ok_or(ShareChainError::BlockNotFound)?; + if block.verified { + return Ok(()); + } + let mut verified = true; + + // lets check the total accumulated difficulty + let mut total_work = AccumulatedDifficulty::from_u128(block.target_difficulty.as_u64() as u128) + .expect("Difficulty will always fit into accumulated difficulty"); + for uncle in block.uncles.iter() { + let uncle_block = self + .get_block_at_height(uncle.0, &uncle.1) + .ok_or(ShareChainError::BlockNotFound)?; + total_work = total_work + .checked_add_difficulty(uncle_block.target_difficulty) + .ok_or(ShareChainError::DifficultyOverflow)?; + } + + // special edge case for start, there is no parent + if height == 0 { + if block.total_pow.as_u128() != total_work.as_u128() { + return Err(ShareChainError::BlockTotalWorkMismatch); + } + let mut actual_block = block.deref().clone(); + // lets replace this + actual_block.verified = verified; + let level = self + .level_at_height_mut(height) + .ok_or(ShareChainError::BlockLevelNotFound)?; + level.blocks.insert(hash, Arc::new(actual_block)); + return Ok(()); + } + + let parent = self + .get_block_at_height(block.height.saturating_sub(1), &block.prev_hash) + .ok_or(ShareChainError::BlockNotFound)?; + + if block.total_pow.as_u128() != parent.total_pow.as_u128() + total_work.as_u128() { + return Err(ShareChainError::BlockTotalWorkMismatch); } - if !missing_parents.is_empty() { - return Err(ShareChainError::BlockParentDoesNotExist { missing_parents }); + + if verified { + let mut actual_block = block.deref().clone(); + // lets replace this + actual_block.verified = verified; + let level = self + .level_at_height_mut(height) + .ok_or(ShareChainError::BlockLevelNotFound)?; + level.blocks.insert(hash, Arc::new(actual_block)); } - Ok((new_tip, missing_parents, next_level_data)) + + Ok(()) } - fn add_block_inner(&mut self, block: Arc) -> Result { + fn add_block_inner(&mut self, block: Arc) -> Result { let new_block_height = block.height; let block_hash = block.hash; // edge case no current chain, lets just add @@ -587,7 +622,7 @@ impl P2Chain { if !self.is_full() { while self.levels.back().expect("we already checked its not empty").height > block.height + 1 { if self.is_full() { - return Ok(false); + return Ok(ChainAddResult::default()); } let level = P2ChainLevel::new_empty( self.levels @@ -600,7 +635,7 @@ impl P2Chain { } if self.levels.back().map(|level| level.height).unwrap_or(0) > block.height { if self.is_full() { - return Ok(false); + return Ok(ChainAddResult::default()); } let level = P2ChainLevel::new(block); self.levels.push_back(level); @@ -612,10 +647,9 @@ impl P2Chain { self.verify_chain(new_block_height, block_hash) } - pub fn add_block_to_chain(&mut self, block: Arc) -> Result { + pub fn add_block_to_chain(&mut self, block: Arc) -> Result { let new_block_height = block.height; let block_hash = block.hash; - let mut new_tip = false; // lets check where this is, do we need to store it in the sync store let first_index = self.levels.back().map(|level| level.height).unwrap_or(0); @@ -650,7 +684,7 @@ impl P2Chain { break 'outer_loop; } - let mut missing_parents = Vec::new(); + let mut new_tip = ChainAddResult::default(); if blocks_to_add.len() > 150 { // we have a potential long chain, lets see if we can do anything with it. for block in blocks_to_add.iter() { @@ -660,12 +694,9 @@ impl P2Chain { .ok_or(ShareChainError::BlockNotFound)? .clone(); match self.add_block_inner(p2_block) { - Err(ShareChainError::BlockParentDoesNotExist { - missing_parents: mut missing, - }) => missing_parents.append(&mut missing), Err(e) => return Err(e), - Ok(_) => { - new_tip = true; + Ok(tip) => { + new_tip.combine(tip); }, } } @@ -676,7 +707,9 @@ impl P2Chain { is_parent_in_main_chain = self.level_at_height(parent_block.height).unwrap().chain_block == block.prev_hash; } else { - missing_parents.push((new_block_height.saturating_sub(1), block.prev_hash)); + new_tip + .missing_blocks + .insert(block.prev_hash, new_block_height.saturating_sub(1)); } // now lets check the uncles for uncle in block.uncles.iter() { @@ -691,13 +724,10 @@ impl P2Chain { } } } else { - missing_parents.push((uncle.0, uncle.1)); + new_tip.missing_blocks.insert(uncle.1, uncle.0); } } - if !missing_parents.is_empty() { - return Err(ShareChainError::BlockParentDoesNotExist { missing_parents }); - } return Ok(new_tip); } @@ -724,7 +754,16 @@ impl P2Chain { } pub fn get_tip(&self) -> Option<&P2ChainLevel> { - self.level_at_height(self.current_tip) + match self.level_at_height(self.current_tip) { + Some(level) => { + if level.chain_block == FixedHash::zero() { + None + } else { + Some(level) + } + }, + None => None, + } } pub fn get_height(&self) -> u64 { @@ -771,7 +810,6 @@ impl P2Chain { #[cfg(test)] mod test { - use tari_common_types::types::BlockHash; use tari_core::{ blocks::{Block, BlockHeader}, proof_of_work::{Difficulty, DifficultyAdjustment}, @@ -780,25 +818,25 @@ mod test { use tari_utilities::epoch_time::EpochTime; use super::*; - use crate::sharechain::in_memory::test::new_random_address; + use crate::sharechain::{in_memory::test::new_random_address, p2block::P2BlockBuilder}; #[test] fn test_only_keeps_size() { let mut chain = P2Chain::new_empty(10, 5); let mut tari_block = Block::new(BlockHeader::new(0), AggregateBody::empty()); - let mut prev_hash = BlockHash::zero(); + let mut prev_block = None; for i in 0..41 { tari_block.header.nonce = i; let address = new_random_address(); - let block = P2Block::builder() + let block = P2BlockBuilder::new(prev_block.as_ref()) .with_timestamp(EpochTime::now()) .with_height(i) .with_miner_wallet_address(address.clone()) .with_tari_block(tari_block.clone()) .unwrap() - .with_prev_hash(prev_hash) - .build(); - prev_hash = block.generate_hash(); + .build() + .unwrap(); + prev_block = Some((*block).clone()); chain.add_block_to_chain(block.clone()).unwrap(); } @@ -819,20 +857,20 @@ mod test { fn get_tips() { let mut chain = P2Chain::new_empty(10, 5); - let mut prev_hash = BlockHash::zero(); + let mut prev_block = None; let mut tari_block = Block::new(BlockHeader::new(0), AggregateBody::empty()); for i in 0..30 { tari_block.header.nonce = i; let address = new_random_address(); - let block = P2Block::builder() + let block = P2BlockBuilder::new(prev_block.as_ref()) .with_timestamp(EpochTime::now()) .with_height(i) .with_tari_block(tari_block.clone()) .unwrap() .with_miner_wallet_address(address.clone()) - .with_prev_hash(prev_hash) - .build(); - prev_hash = block.generate_hash(); + .build() + .unwrap(); + prev_block = Some((*block).clone()); chain.add_block_to_chain(block.clone()).unwrap(); let level = chain.get_tip().unwrap(); @@ -843,41 +881,46 @@ mod test { #[test] fn test_does_not_set_tip_unless_full_chain() { + // we have a window of 5, meaing that we need 5 valid blocks + // if we dont start at 0, we need a chain of at least 6 blocks let mut chain = P2Chain::new_empty(10, 5); - let mut prev_hash = BlockHash::zero(); + let mut prev_block = None; let mut tari_block = Block::new(BlockHeader::new(0), AggregateBody::empty()); - for i in 1..5 { + for i in 1..6 { tari_block.header.nonce = i; let address = new_random_address(); - let block = P2Block::builder() + let block = P2BlockBuilder::new(prev_block.as_ref()) .with_timestamp(EpochTime::now()) .with_height(i) .with_tari_block(tari_block.clone()) .unwrap() .with_miner_wallet_address(address.clone()) - .with_prev_hash(prev_hash) - .build(); - prev_hash = block.generate_hash(); + .build() + .unwrap(); + prev_block = Some((*block).clone()); chain.add_block_to_chain(block.clone()).unwrap(); + assert!(chain.get_tip().is_none()); } - tari_block.header.nonce = 5; + tari_block.header.nonce = 6; let address = new_random_address(); - let block = P2Block::builder() + let block = P2BlockBuilder::new(prev_block.as_ref()) .with_timestamp(EpochTime::now()) - .with_height(5) + .with_height(6) .with_tari_block(tari_block.clone()) .unwrap() .with_miner_wallet_address(address.clone()) - .with_prev_hash(prev_hash) - .build(); + .build() + .unwrap(); chain.add_block_to_chain(block.clone()).unwrap(); let level = chain.get_tip().unwrap(); - assert_eq!(level.height, 5); + assert_eq!(level.height, 6); // the whole chain must be verified chain.assert_share_window_verified(); + // first block should not be verified + assert!(!chain.get_chain_block_at_height(1).unwrap().verified); } #[test] @@ -888,36 +931,35 @@ mod test { // window + 1 blocks in chain-- let mut chain = P2Chain::new_empty(10, 5); - let mut prev_hash = BlockHash::zero(); + let mut prev_block = None; let mut tari_block = Block::new(BlockHeader::new(0), AggregateBody::empty()); let mut blocks = Vec::new(); for i in 0..7 { tari_block.header.nonce = i; let address = new_random_address(); - let block = P2Block::builder() + let block = P2BlockBuilder::new(prev_block.as_ref()) .with_timestamp(EpochTime::now()) .with_height(i) .with_tari_block(tari_block.clone()) .unwrap() .with_miner_wallet_address(address.clone()) - .with_prev_hash(prev_hash) - .build(); - prev_hash = block.generate_hash(); + .build() + .unwrap(); + prev_block = Some((*block).clone()); blocks.push(block.clone()); } - chain.add_block_to_chain(blocks[6].clone()).unwrap_err(); + chain.add_block_to_chain(blocks[6].clone()).unwrap(); assert!(chain.get_tip().is_none()); assert_eq!(chain.current_tip, 0); assert_eq!(chain.levels.len(), 1); assert_eq!(chain.levels[0].height, 6); for i in (2..6).rev() { - chain.add_block_to_chain(blocks[i].clone()).unwrap_err(); + chain.add_block_to_chain(blocks[i].clone()).unwrap(); assert!(chain.get_tip().is_none()); assert_eq!(chain.current_tip, 0); } - - chain.add_block_to_chain(blocks[1].clone()).unwrap_err(); + chain.add_block_to_chain(blocks[1].clone()).unwrap(); let level = chain.get_tip().unwrap(); assert_eq!(level.height, 6); @@ -930,29 +972,29 @@ mod test { // to test this properly we need 6 blocks in the chain, and not use 0 as zero will always be valid and counter // as chain start block height 2 will only be valid if it has parents aka block 1, so we need share // window + 1 blocks in chain-- - let mut chain = P2Chain::new_empty(10, 5); + let mut chain = P2Chain::new_empty(20, 10); - let mut prev_hash = BlockHash::zero(); + let mut prev_block = None; let mut tari_block = Block::new(BlockHeader::new(0), AggregateBody::empty()); let mut blocks = Vec::new(); for i in 0..20 { tari_block.header.nonce = i; let address = new_random_address(); - let block = P2Block::builder() + let block = P2BlockBuilder::new(prev_block.as_ref()) .with_timestamp(EpochTime::now()) .with_height(i) .with_tari_block(tari_block.clone()) .unwrap() .with_miner_wallet_address(address.clone()) - .with_prev_hash(prev_hash) - .build(); - prev_hash = block.generate_hash(); + .build() + .unwrap(); + prev_block = Some((*block).clone()); blocks.push(block.clone()); } for i in 0..9 { chain.add_block_to_chain(blocks[i].clone()).unwrap(); assert_eq!(chain.get_tip().unwrap().height, i as u64); - chain.add_block_to_chain(blocks[19 - i].clone()).unwrap_err(); + chain.add_block_to_chain(blocks[19 - i].clone()).unwrap(); assert_eq!(chain.get_tip().unwrap().height, i as u64); } @@ -973,60 +1015,61 @@ mod test { // window + 1 blocks in chain-- let mut chain = P2Chain::new_empty(10, 5); - let mut prev_hash = BlockHash::zero(); + let mut prev_block = None; let mut tari_block = Block::new(BlockHeader::new(0), AggregateBody::empty()); let mut blocks = Vec::new(); for i in 0..6 { tari_block.header.nonce = i; let address = new_random_address(); - let block = P2Block::builder() + let block = P2BlockBuilder::new(prev_block.as_ref()) .with_timestamp(EpochTime::now()) .with_height(i) .with_tari_block(tari_block.clone()) .unwrap() .with_miner_wallet_address(address.clone()) - .with_prev_hash(prev_hash) - .build(); - prev_hash = block.generate_hash(); + .build() + .unwrap(); + prev_block = Some((*block).clone()); blocks.push(block.clone()); } tari_block.header.nonce = 55; let address = new_random_address(); - let uncle_block = P2Block::builder() + let uncle_block = P2BlockBuilder::new(Some(&blocks[4])) .with_timestamp(EpochTime::now()) .with_height(5) .with_tari_block(tari_block.clone()) .unwrap() .with_miner_wallet_address(address.clone()) - .with_prev_hash(blocks[4].hash) - .build(); + .build() + .unwrap(); tari_block.header.nonce = 6; let address = new_random_address(); - let block = P2Block::builder() + let block = P2BlockBuilder::new(prev_block.as_ref()) .with_timestamp(EpochTime::now()) .with_height(6) .with_tari_block(tari_block.clone()) .unwrap() .with_miner_wallet_address(address.clone()) - .with_prev_hash(prev_hash) - .with_uncles(vec![(5, uncle_block.hash)]) - .build(); + .with_uncles(&vec![uncle_block.clone()]) + .unwrap() + .build() + .unwrap(); blocks.push(block.clone()); - chain.add_block_to_chain(blocks[6].clone()).unwrap_err(); + chain.add_block_to_chain(blocks[6].clone()).unwrap(); assert!(chain.get_tip().is_none()); assert_eq!(chain.current_tip, 0); assert_eq!(chain.levels.len(), 1); assert_eq!(chain.levels[0].height, 6); for i in (2..6).rev() { - chain.add_block_to_chain(blocks[i].clone()).unwrap_err(); + chain.add_block_to_chain(blocks[i].clone()).unwrap(); assert!(chain.get_tip().is_none()); assert_eq!(chain.current_tip, 0); } - chain.add_block_to_chain(blocks[1].clone()).unwrap_err(); + chain.add_block_to_chain(blocks[1].clone()).unwrap(); assert!(chain.get_tip().is_none()); chain.add_block_to_chain(uncle_block).unwrap(); @@ -1037,23 +1080,22 @@ mod test { #[test] fn test_dont_set_tip_on_single_high_height() { - println!("hello?"); let mut chain = P2Chain::new_empty(10, 5); - let mut prev_hash = BlockHash::zero(); + let mut prev_block = None; let mut tari_block = Block::new(BlockHeader::new(0), AggregateBody::empty()); for i in 0..5 { tari_block.header.nonce = i; let address = new_random_address(); - let block = P2Block::builder() + let block = P2BlockBuilder::new(prev_block.as_ref()) .with_timestamp(EpochTime::now()) .with_height(i) .with_tari_block(tari_block.clone()) .unwrap() .with_miner_wallet_address(address.clone()) - .with_prev_hash(prev_hash) - .build(); - prev_hash = block.generate_hash(); + .build() + .unwrap(); + prev_block = Some((*block).clone()); chain.add_block_to_chain(block.clone()).unwrap(); let level = chain.get_tip().unwrap(); @@ -1061,52 +1103,52 @@ mod test { } // we do this so we can add a missing parent or 2 let address = new_random_address(); - let block = P2Block::builder() + let block = P2BlockBuilder::new(prev_block.as_ref()) .with_timestamp(EpochTime::now()) .with_height(100) .with_tari_block(tari_block.clone()) .unwrap() .with_miner_wallet_address(address.clone()) - .with_prev_hash(prev_hash) - .build(); - prev_hash = block.generate_hash(); + .build() + .unwrap(); + prev_block = Some((*block).clone()); let address = new_random_address(); - let block = P2Block::builder() + let block = P2BlockBuilder::new(prev_block.as_ref()) .with_timestamp(EpochTime::now()) .with_height(2000) .with_tari_block(tari_block.clone()) .unwrap() .with_miner_wallet_address(address.clone()) - .with_prev_hash(prev_hash) - .build(); - prev_hash = block.generate_hash(); + .build() + .unwrap(); + prev_block = Some((*block).clone()); - chain.add_block_to_chain(block.clone()).unwrap_err(); + chain.add_block_to_chain(block.clone()).unwrap(); let level = chain.get_tip().unwrap(); assert_eq!(level.height, 4); let address = new_random_address(); - let block = P2Block::builder() + let block = P2BlockBuilder::new(prev_block.as_ref()) .with_timestamp(EpochTime::now()) .with_height(1000) .with_tari_block(tari_block.clone()) .unwrap() .with_miner_wallet_address(address.clone()) - .with_prev_hash(prev_hash) - .build(); - prev_hash = block.generate_hash(); + .build() + .unwrap(); + prev_block = Some((*block).clone()); let address = new_random_address(); - let block = P2Block::builder() + let block = P2BlockBuilder::new(prev_block.as_ref()) .with_timestamp(EpochTime::now()) .with_height(20000) .with_tari_block(tari_block.clone()) .unwrap() .with_miner_wallet_address(address.clone()) - .with_prev_hash(prev_hash) - .build(); + .build() + .unwrap(); - chain.add_block_to_chain(block.clone()).unwrap_err(); + chain.add_block_to_chain(block.clone()).unwrap(); let level = chain.get_tip().unwrap(); assert_eq!(level.height, 4); @@ -1116,21 +1158,21 @@ mod test { fn get_parent() { let mut chain = P2Chain::new_empty(10, 5); - let mut prev_hash = BlockHash::zero(); + let mut prev_block = None; let mut tari_block = Block::new(BlockHeader::new(0), AggregateBody::empty()); for i in 0..41 { tari_block.header.nonce = i; let address = new_random_address(); - let block = P2Block::builder() + let block = P2BlockBuilder::new(prev_block.as_ref()) .with_timestamp(EpochTime::now()) .with_height(i) .with_miner_wallet_address(address.clone()) - .with_prev_hash(prev_hash) .with_tari_block(tari_block.clone()) .unwrap() - .build(); + .build() + .unwrap(); - prev_hash = block.generate_hash(); + prev_block = Some((*block).clone()); chain.add_block_to_chain(block.clone()).unwrap(); } @@ -1151,20 +1193,21 @@ mod test { let mut chain = P2Chain::new_empty(10, 5); let mut timestamp = EpochTime::now(); - let mut prev_hash = BlockHash::zero(); + let mut prev_block = None; for i in 0..32 { let address = new_random_address(); timestamp = timestamp.checked_add(EpochTime::from(10)).unwrap(); - let block = P2Block::builder() + let block = P2BlockBuilder::new(prev_block.as_ref()) .with_timestamp(timestamp) .with_height(i) .with_miner_wallet_address(address.clone()) .with_target_difficulty(Difficulty::from_u64(i + 1).unwrap()) - .with_prev_hash(prev_hash) - .build(); + .unwrap() + .build() + .unwrap(); - prev_hash = block.generate_hash(); + prev_block = Some((*block).clone()); chain.add_block_to_chain(block).unwrap(); @@ -1181,24 +1224,25 @@ mod test { let mut chain = P2Chain::new_empty(10, 5); let mut timestamp = EpochTime::now(); - let mut prev_hash = BlockHash::zero(); + let mut prev_block = None; let mut tari_block = Block::new(BlockHeader::new(0), AggregateBody::empty()); for i in 0..32 { tari_block.header.nonce = i; let address = new_random_address(); timestamp = timestamp.checked_add(EpochTime::from(10)).unwrap(); - let block = P2Block::builder() + let block = P2BlockBuilder::new(prev_block.as_ref()) .with_timestamp(timestamp) .with_height(i) .with_miner_wallet_address(address.clone()) .with_target_difficulty(Difficulty::from_u64(10).unwrap()) + .unwrap() .with_tari_block(tari_block.clone()) .unwrap() - .with_prev_hash(prev_hash) - .build(); + .build() + .unwrap(); - prev_hash = block.generate_hash(); + prev_block = Some((*block).clone()); chain.add_block_to_chain(block).unwrap(); } let level = chain.get_tip().unwrap(); @@ -1210,28 +1254,29 @@ mod test { assert_eq!(level.block_in_main_chain().unwrap().original_header.nonce, 31); assert_eq!(level.block_in_main_chain().unwrap().height, 31); assert_eq!( - chain.total_accumulated_tip_difficulty, - AccumulatedDifficulty::from_u128(50).unwrap() // 31+30+29+28+27 + chain.total_accumulated_tip_difficulty(), + AccumulatedDifficulty::from_u128(320).unwrap() ); let block_29 = chain.level_at_height(29).unwrap().block_in_main_chain().unwrap(); - prev_hash = block_29.generate_hash(); + prev_block = Some((**block_29).clone()); timestamp = block_29.timestamp; let address = new_random_address(); timestamp = timestamp.checked_add(EpochTime::from(10)).unwrap(); tari_block.header.nonce = 30 * 2; - let block = P2Block::builder() + let block = P2BlockBuilder::new(prev_block.as_ref()) .with_timestamp(timestamp) .with_height(30) .with_miner_wallet_address(address.clone()) .with_target_difficulty(Difficulty::from_u64(9).unwrap()) + .unwrap() .with_tari_block(tari_block.clone()) .unwrap() - .with_prev_hash(prev_hash) - .build(); + .build() + .unwrap(); - prev_hash = block.generate_hash(); + prev_block = Some((*block).clone()); chain.add_block_to_chain(block).unwrap(); let level = chain.get_tip().unwrap(); @@ -1242,15 +1287,16 @@ mod test { tari_block.header.nonce = 31 * 2; timestamp = timestamp.checked_add(EpochTime::from(10)).unwrap(); - let block = P2Block::builder() + let block = P2BlockBuilder::new(prev_block.as_ref()) .with_timestamp(timestamp) .with_height(31) .with_miner_wallet_address(address.clone()) .with_target_difficulty(Difficulty::from_u64(32).unwrap()) + .unwrap() .with_tari_block(tari_block.clone()) .unwrap() - .with_prev_hash(prev_hash) - .build(); + .build() + .unwrap(); chain.add_block_to_chain(block).unwrap(); let level = chain.get_tip().unwrap(); @@ -1263,8 +1309,8 @@ mod test { assert_eq!(level.block_in_main_chain().unwrap().original_header.nonce, 31 * 2); assert_eq!(level.block_in_main_chain().unwrap().height, 31); assert_eq!( - chain.total_accumulated_tip_difficulty, - AccumulatedDifficulty::from_u128(71).unwrap() // 32+9+10+10+10 + chain.total_accumulated_tip_difficulty(), + AccumulatedDifficulty::from_u128(341).unwrap() ); } @@ -1273,26 +1319,27 @@ mod test { let mut chain = P2Chain::new_empty(10, 5); let mut timestamp = EpochTime::now(); - let mut prev_hash = BlockHash::zero(); + let mut prev_block = None; for i in 1..15 { let address = new_random_address(); timestamp = timestamp.checked_add(EpochTime::from(10)).unwrap(); - let block = P2Block::builder() + let block = P2BlockBuilder::new(prev_block.as_ref()) .with_timestamp(timestamp) .with_height(i) .with_miner_wallet_address(address.clone()) .with_target_difficulty(Difficulty::from_u64(10).unwrap()) - .with_prev_hash(prev_hash) - .build(); + .unwrap() + .build() + .unwrap(); - prev_hash = block.generate_hash(); + prev_block = Some((*block).clone()); chain.add_block_to_chain(block).unwrap(); } assert_eq!( - chain.total_accumulated_tip_difficulty, - AccumulatedDifficulty::from_u128(50).unwrap() + chain.total_accumulated_tip_difficulty(), + AccumulatedDifficulty::from_u128(140).unwrap() //(10)*15 ); } @@ -1301,35 +1348,38 @@ mod test { let mut chain = P2Chain::new_empty(10, 5); let mut timestamp = EpochTime::now(); - let mut prev_hash = BlockHash::zero(); + let mut prev_block = None; for i in 0..10 { let address = new_random_address(); timestamp = timestamp.checked_add(EpochTime::from(10)).unwrap(); let mut uncles = Vec::new(); if i > 1 { - let prev_hash_uncle = chain.level_at_height(i - 2).unwrap().chain_block; + let prev_uncle = chain.level_at_height(i - 2).unwrap().block_in_main_chain().unwrap(); // lets create an uncle block - let block = P2Block::builder() + let block = P2BlockBuilder::new(Some(&prev_uncle)) .with_timestamp(timestamp) .with_height(i - 1) .with_miner_wallet_address(address.clone()) .with_target_difficulty(Difficulty::from_u64(9).unwrap()) - .with_prev_hash(prev_hash_uncle) - .build(); - uncles.push((i - 1, block.hash)); + .unwrap() + .build() + .unwrap(); + uncles.push(block.clone()); chain.add_block_to_chain(block).unwrap(); } - let block = P2Block::builder() + let block = P2BlockBuilder::new(prev_block.as_ref()) .with_timestamp(timestamp) .with_height(i) .with_miner_wallet_address(address.clone()) .with_target_difficulty(Difficulty::from_u64(10).unwrap()) - .with_uncles(uncles) - .with_prev_hash(prev_hash) - .build(); + .unwrap() + .with_uncles(&uncles) + .unwrap() + .build() + .unwrap(); - prev_hash = block.generate_hash(); + prev_block = Some((*block).clone()); chain.add_block_to_chain(block).unwrap(); } @@ -1340,8 +1390,60 @@ mod test { ); assert_eq!(level.block_in_main_chain().unwrap().height, 9); assert_eq!( - chain.total_accumulated_tip_difficulty, - AccumulatedDifficulty::from_u128(95).unwrap() //(10+9)*5 + chain.total_accumulated_tip_difficulty(), + AccumulatedDifficulty::from_u128(172).unwrap() + ); + } + + #[test] + fn calculate_total_difficulty_correctly_with_wrapping_blocks() { + let mut chain = P2Chain::new_empty(10, 5); + + let mut timestamp = EpochTime::now(); + let mut prev_block = None; + + for i in 0..20 { + let address = new_random_address(); + timestamp = timestamp.checked_add(EpochTime::from(10)).unwrap(); + let mut uncles = Vec::new(); + if i > 1 { + let prev_uncle = chain.level_at_height(i - 2).unwrap().block_in_main_chain().unwrap(); + // lets create an uncle block + let block = P2BlockBuilder::new(Some(&prev_uncle)) + .with_timestamp(timestamp) + .with_height(i - 1) + .with_miner_wallet_address(address.clone()) + .with_target_difficulty(Difficulty::from_u64(9).unwrap()) + .unwrap() + .build() + .unwrap(); + uncles.push(block.clone()); + chain.add_block_to_chain(block).unwrap(); + } + let block = P2BlockBuilder::new(prev_block.as_ref()) + .with_timestamp(timestamp) + .with_height(i) + .with_miner_wallet_address(address.clone()) + .with_target_difficulty(Difficulty::from_u64(10).unwrap()) + .unwrap() + .with_uncles(&uncles) + .unwrap() + .build() + .unwrap(); + + prev_block = Some((*block).clone()); + + chain.add_block_to_chain(block).unwrap(); + } + let level = chain.get_tip().unwrap(); + assert_eq!( + level.block_in_main_chain().unwrap().target_difficulty, + Difficulty::from_u64(10).unwrap() + ); + assert_eq!(level.block_in_main_chain().unwrap().height, 19); + assert_eq!( + chain.total_accumulated_tip_difficulty(), + AccumulatedDifficulty::from_u128(362).unwrap() //(10+9)*20 - (9*2) ); } @@ -1350,35 +1452,38 @@ mod test { let mut chain = P2Chain::new_empty(10, 5); let mut timestamp = EpochTime::now(); - let mut prev_hash = BlockHash::zero(); + let mut prev_block = None; for i in 0..10 { let address = new_random_address(); timestamp = timestamp.checked_add(EpochTime::from(10)).unwrap(); let mut uncles = Vec::new(); if i > 1 { - let prev_hash_uncle = chain.level_at_height(i - 2).unwrap().chain_block; + let prev_uncle = chain.level_at_height(i - 2).unwrap().block_in_main_chain().unwrap(); // lets create an uncle block - let block = P2Block::builder() + let block = P2BlockBuilder::new(Some(&prev_uncle)) .with_timestamp(timestamp) .with_height(i - 1) .with_miner_wallet_address(address.clone()) .with_target_difficulty(Difficulty::from_u64(9).unwrap()) - .with_prev_hash(prev_hash_uncle) - .build(); - uncles.push((i - 1, block.hash)); + .unwrap() + .build() + .unwrap(); + uncles.push(block.clone()); chain.add_block_to_chain(block).unwrap(); } - let block = P2Block::builder() + let block = P2BlockBuilder::new(prev_block.as_ref()) .with_timestamp(timestamp) .with_height(i) .with_miner_wallet_address(address.clone()) .with_target_difficulty(Difficulty::from_u64(10).unwrap()) - .with_uncles(uncles) - .with_prev_hash(prev_hash) - .build(); + .unwrap() + .with_uncles(&uncles) + .unwrap() + .build() + .unwrap(); - prev_hash = block.generate_hash(); + prev_block = Some((*block).clone()); chain.add_block_to_chain(block).unwrap(); } @@ -1386,48 +1491,54 @@ mod test { let address = new_random_address(); timestamp = timestamp.checked_add(EpochTime::from(10)).unwrap(); let mut uncles = Vec::new(); - let prev_hash_uncle = chain.level_at_height(6).unwrap().chain_block; + let prev_uncle = chain.level_at_height(6).unwrap().block_in_main_chain().unwrap(); // lets create an uncle block - let block = P2Block::builder() + let block = P2BlockBuilder::new(Some(&prev_uncle)) .with_timestamp(timestamp) .with_height(7) .with_miner_wallet_address(address.clone()) .with_target_difficulty(Difficulty::from_u64(10).unwrap()) - .with_prev_hash(prev_hash_uncle) - .build(); - uncles.push((7, block.hash)); + .unwrap() + .build() + .unwrap(); + uncles.push(block.clone()); chain.add_block_to_chain(block).unwrap(); - prev_hash = chain.level_at_height(7).unwrap().chain_block; - let block = P2Block::builder() + prev_block = Some((**chain.level_at_height(7).unwrap().block_in_main_chain().unwrap()).clone()); + let block = P2BlockBuilder::new(prev_block.as_ref()) .with_timestamp(timestamp) .with_height(8) .with_miner_wallet_address(address.clone()) .with_target_difficulty(Difficulty::from_u64(11).unwrap()) - .with_uncles(uncles) - .with_prev_hash(prev_hash) - .build(); - let new_block_hash = block.hash; + .unwrap() + .with_uncles(&uncles) + .unwrap() + .build() + .unwrap(); + let new_block = block.clone(); chain.add_block_to_chain(block).unwrap(); // lets create an uncle block let mut uncles = Vec::new(); - let block = P2Block::builder() + let block = P2BlockBuilder::new(prev_block.as_ref()) .with_timestamp(timestamp) .with_height(8) .with_miner_wallet_address(address.clone()) .with_target_difficulty(Difficulty::from_u64(10).unwrap()) - .with_prev_hash(prev_hash) - .build(); - uncles.push((8, block.hash)); + .unwrap() + .build() + .unwrap(); + uncles.push(block.clone()); chain.add_block_to_chain(block).unwrap(); - let block = P2Block::builder() + let block = P2BlockBuilder::new(Some(&new_block)) .with_timestamp(timestamp) .with_height(9) .with_miner_wallet_address(address.clone()) .with_target_difficulty(Difficulty::from_u64(11).unwrap()) - .with_uncles(uncles) - .with_prev_hash(new_block_hash) - .build(); + .unwrap() + .with_uncles(&uncles) + .unwrap() + .build() + .unwrap(); chain.add_block_to_chain(block).unwrap(); let level = chain.get_tip().unwrap(); @@ -1437,8 +1548,8 @@ mod test { ); assert_eq!(level.block_in_main_chain().unwrap().height, 9); assert_eq!( - chain.total_accumulated_tip_difficulty, - AccumulatedDifficulty::from_u128(99).unwrap() //(10+9)*3 +10+11*2 + chain.total_accumulated_tip_difficulty(), + AccumulatedDifficulty::from_u128(176).unwrap() ); } @@ -1446,21 +1557,22 @@ mod test { fn rerog_less_than_share_window() { let mut chain = P2Chain::new_empty(20, 15); - let mut prev_hash = BlockHash::zero(); + let mut prev_block = None; let mut tari_block = Block::new(BlockHeader::new(0), AggregateBody::empty()); for i in 0..10 { tari_block.header.nonce = i; let address = new_random_address(); - let block = P2Block::builder() + let block = P2BlockBuilder::new(prev_block.as_ref()) .with_timestamp(EpochTime::now()) .with_height(i) .with_tari_block(tari_block.clone()) .unwrap() .with_target_difficulty(Difficulty::from_u64(9).unwrap()) + .unwrap() .with_miner_wallet_address(address.clone()) - .with_prev_hash(prev_hash) - .build(); - prev_hash = block.generate_hash(); + .build() + .unwrap(); + prev_block = Some((*block).clone()); chain.add_block_to_chain(block.clone()).unwrap(); let level = chain.get_tip().unwrap(); @@ -1468,24 +1580,25 @@ mod test { assert_eq!(level.block_in_main_chain().unwrap().original_header.nonce, i); } - assert_eq!(chain.total_accumulated_tip_difficulty.as_u128(), 90); + assert_eq!(chain.total_accumulated_tip_difficulty().as_u128(), 90); // lets create a new chain to reorg to - let mut prev_hash = BlockHash::zero(); + let mut prev_block = None; let mut tari_block = Block::new(BlockHeader::new(0), AggregateBody::empty()); for i in 0..10 { tari_block.header.nonce = i + 100; let address = new_random_address(); - let block = P2Block::builder() + let block = P2BlockBuilder::new(prev_block.as_ref()) .with_timestamp(EpochTime::now()) .with_height(i) .with_tari_block(tari_block.clone()) .unwrap() .with_target_difficulty(Difficulty::from_u64(10).unwrap()) + .unwrap() .with_miner_wallet_address(address.clone()) - .with_prev_hash(prev_hash) - .build(); - prev_hash = block.generate_hash(); + .build() + .unwrap(); + prev_block = Some((*block).clone()); chain.add_block_to_chain(block.clone()).unwrap(); let level = chain.get_tip().unwrap(); @@ -1499,28 +1612,29 @@ mod test { assert_eq!(level.block_in_main_chain().unwrap().original_header.nonce, 109); } } - assert_eq!(chain.total_accumulated_tip_difficulty.as_u128(), 100); + assert_eq!(chain.total_accumulated_tip_difficulty().as_u128(), 100); } #[test] fn rests_levels_after_reorg() { let mut chain = P2Chain::new_empty(20, 15); - let mut prev_hash = BlockHash::zero(); + let mut prev_block = None; let mut tari_block = Block::new(BlockHeader::new(0), AggregateBody::empty()); for i in 0..10 { tari_block.header.nonce = i; let address = new_random_address(); - let block = P2Block::builder() + let block = P2BlockBuilder::new(prev_block.as_ref()) .with_timestamp(EpochTime::now()) .with_height(i) .with_tari_block(tari_block.clone()) .unwrap() .with_target_difficulty(Difficulty::from_u64(9).unwrap()) + .unwrap() .with_miner_wallet_address(address.clone()) - .with_prev_hash(prev_hash) - .build(); - prev_hash = block.generate_hash(); + .build() + .unwrap(); + prev_block = Some((*block).clone()); chain.add_block_to_chain(block.clone()).unwrap(); let level = chain.get_tip().unwrap(); @@ -1529,30 +1643,31 @@ mod test { } let level = chain.get_tip().unwrap(); assert_eq!(level.height, 9); - assert_eq!(chain.total_accumulated_tip_difficulty.as_u128(), 90); - assert_eq!(chain.level_at_height(9).unwrap().chain_block, prev_hash); + assert_eq!(chain.total_accumulated_tip_difficulty().as_u128(), 90); + assert_eq!(chain.level_at_height(9).unwrap().chain_block, prev_block.unwrap().hash); // lets create a new tip to reorg to branching off 2 from the tip - let mut prev_hash = chain.level_at_height(7).unwrap().chain_block; + let mut prev_block = Some((**chain.level_at_height(7).unwrap().block_in_main_chain().unwrap()).clone()); let mut tari_block = Block::new(BlockHeader::new(0), AggregateBody::empty()); tari_block.header.nonce = 100; let address = new_random_address(); - let block = P2Block::builder() + let block = P2BlockBuilder::new(prev_block.as_ref()) .with_timestamp(EpochTime::now()) .with_height(8) .with_tari_block(tari_block.clone()) .unwrap() .with_target_difficulty(Difficulty::from_u64(100).unwrap()) + .unwrap() .with_miner_wallet_address(address.clone()) - .with_prev_hash(prev_hash) - .build(); - prev_hash = block.generate_hash(); - assert!(chain.add_block_to_chain(block.clone()).unwrap()); + .build() + .unwrap(); + prev_block = Some((*block).clone()); + assert_eq!(chain.add_block_to_chain(block.clone()).unwrap().missing_blocks.len(), 0); let level = chain.get_tip().unwrap(); assert_eq!(level.height, 8); - assert_eq!(chain.total_accumulated_tip_difficulty.as_u128(), 172); + assert_eq!(chain.total_accumulated_tip_difficulty().as_u128(), 172); assert_eq!(chain.level_at_height(9).unwrap().chain_block, FixedHash::default()); } @@ -1560,7 +1675,7 @@ mod test { fn difficulty_go_up() { let mut chain = P2Chain::new_empty(10, 5); - let mut prev_hash = BlockHash::zero(); + let mut prev_block = None; let mut tari_block = Block::new(BlockHeader::new(0), AggregateBody::empty()); let mut timestamp = EpochTime::now(); let mut target_difficulty = Difficulty::min(); @@ -1577,16 +1692,17 @@ mod test { assert!(target_difficulty > prev_target_difficulty); } let address = new_random_address(); - let block = P2Block::builder() + let block = P2BlockBuilder::new(prev_block.as_ref()) .with_timestamp(timestamp) .with_height(i) .with_tari_block(tari_block.clone()) .unwrap() .with_miner_wallet_address(address.clone()) .with_target_difficulty(target_difficulty) - .with_prev_hash(prev_hash) - .build(); - prev_hash = block.generate_hash(); + .unwrap() + .build() + .unwrap(); + prev_block = Some((*block).clone()); chain.add_block_to_chain(block.clone()).unwrap(); let level = chain.get_tip().unwrap(); @@ -1598,7 +1714,7 @@ mod test { fn difficulty_go_down() { let mut chain = P2Chain::new_empty(10, 5); - let mut prev_hash = BlockHash::zero(); + let mut prev_block = None; let mut tari_block = Block::new(BlockHeader::new(0), AggregateBody::empty()); let mut timestamp = EpochTime::now(); let mut target_difficulty = Difficulty::min(); @@ -1615,16 +1731,17 @@ mod test { assert!(target_difficulty < prev_target_difficulty); } let address = new_random_address(); - let block = P2Block::builder() + let block = P2BlockBuilder::new(prev_block.as_ref()) .with_timestamp(timestamp) .with_height(i) .with_tari_block(tari_block.clone()) .unwrap() .with_miner_wallet_address(address.clone()) .with_target_difficulty(target_difficulty) - .with_prev_hash(prev_hash) - .build(); - prev_hash = block.generate_hash(); + .unwrap() + .build() + .unwrap(); + prev_block = Some((*block).clone()); chain.add_block_to_chain(block.clone()).unwrap(); let level = chain.get_tip().unwrap(); @@ -1640,28 +1757,31 @@ mod test { // the tip is not set to the new block, because the uncle is missing. let mut chain = P2Chain::new_empty(10, 5); - let prev_hash = BlockHash::zero(); + let prev_block = None; - let block1 = P2Block::builder() + let block1 = P2BlockBuilder::new(prev_block.as_ref()) .with_height(0) .with_target_difficulty(Difficulty::from_u64(10).unwrap()) - .with_prev_hash(prev_hash) - .build(); + .unwrap() + .build() + .unwrap(); chain.add_block_to_chain(block1.clone()).unwrap(); assert_eq!(chain.current_tip, 0); - let block1_uncle = P2Block::builder() + let block1_uncle = P2BlockBuilder::new(prev_block.as_ref()) .with_height(0) .with_target_difficulty(Difficulty::from_u64(9).unwrap()) - .with_prev_hash(prev_hash) - .build(); + .unwrap() + .build() + .unwrap(); - let block2 = P2Block::builder() + let block2 = P2BlockBuilder::new(Some(&block1)) .with_height(1) - .with_prev_hash(block1.hash) - .with_uncles(vec![(0, block1_uncle.hash)]) - .build(); - chain.add_block_to_chain(block2).unwrap_err(); + .with_uncles(&vec![block1_uncle.clone()]) + .unwrap() + .build() + .unwrap(); + chain.add_block_to_chain(block2).unwrap(); // The tip should still be block 1 because block 2 is missing an uncle assert_eq!(chain.current_tip, 0); } @@ -1669,62 +1789,68 @@ mod test { #[test] fn test_only_reorg_to_chain_if_it_is_verified() { let mut chain = P2Chain::new_empty(10, 5); - let prev_hash = BlockHash::zero(); + let prev_block = None; - let block = P2Block::builder() + let block = P2BlockBuilder::new(prev_block.as_ref()) .with_height(0) .with_target_difficulty(Difficulty::from_u64(10).unwrap()) - .with_prev_hash(prev_hash) - .build(); + .unwrap() + .build() + .unwrap(); chain.add_block_to_chain(block.clone()).unwrap(); - let block2 = P2Block::builder() + let block2 = P2BlockBuilder::new(Some(&block)) .with_height(1) .with_target_difficulty(Difficulty::from_u64(10).unwrap()) - .with_prev_hash(block.hash) - .build(); + .unwrap() + .build() + .unwrap(); chain.add_block_to_chain(block2.clone()).unwrap(); - let missing_uncle = P2Block::builder() + let missing_uncle = P2BlockBuilder::new(prev_block.as_ref()) .with_height(0) .with_target_difficulty(diff(111)) - .with_prev_hash(prev_hash) - .build(); + .unwrap() + .build() + .unwrap(); - let unverified_uncle = P2Block::builder() + let unverified_uncle = P2BlockBuilder::new(Some(&missing_uncle)) .with_height(1) - .with_target_difficulty(Difficulty::from_u64(100).unwrap()) // otherwise hash is the same - .with_prev_hash(missing_uncle.hash) - .build(); + .with_target_difficulty(Difficulty::from_u64(100).unwrap()) + .unwrap() + .build() + .unwrap(); - let block2b = P2Block::builder() + let block2b = P2BlockBuilder::new(Some(&block)) .with_height(1) .with_target_difficulty(Difficulty::from_u64(11).unwrap()) - .with_prev_hash(block.hash) - // .with_uncles(vec![(0, missing_uncle.hash)]) - .build(); + .unwrap() + .build() + .unwrap(); - let block3b = P2Block::builder() + let block3b = P2BlockBuilder::new(Some(&block2b)) .with_height(2) .with_target_difficulty(diff(100)) - .with_prev_hash(block2b.hash) - .with_uncles(vec![(0, unverified_uncle.hash)]) - .build(); + .unwrap() + .with_uncles(&vec![unverified_uncle.clone()]) + .unwrap() + .build() + .unwrap(); assert_eq!(chain.current_tip, 1); assert_eq!(chain.get_tip().unwrap().chain_block, block2.hash); - chain.add_block_to_chain(block3b).unwrap_err(); + chain.add_block_to_chain(block3b).unwrap(); // Check that we don't reorg assert_eq!(chain.current_tip, 1); assert_eq!(chain.get_tip().unwrap().chain_block, block2.hash); - chain.add_block_to_chain(unverified_uncle).unwrap_err(); + chain.add_block_to_chain(unverified_uncle).unwrap(); // Now add block 2b - chain.add_block_to_chain(block2b.clone()).unwrap_err(); + chain.add_block_to_chain(block2b.clone()).unwrap(); // But chain tip should not be 3b because it is not verified assert_eq!(chain.current_tip, 1); assert_eq!(chain.get_tip().unwrap().chain_block, block2b.hash); diff --git a/src/sharechain/p2chain_level.rs b/src/sharechain/p2chain_level.rs index 3a44cfe..0ddb6ce 100644 --- a/src/sharechain/p2chain_level.rs +++ b/src/sharechain/p2chain_level.rs @@ -56,7 +56,7 @@ impl P2ChainLevel { } } - pub fn add_block(&mut self, block: Arc) -> Result<(), crate::sharechain::error::ShareChainError> { + pub fn add_block(&mut self, block: Arc) -> Result<(), ShareChainError> { if self.height != block.height { return Err(ShareChainError::InvalidBlock { reason: "Block height does not match the chain level height".to_string(), @@ -75,16 +75,21 @@ impl P2ChainLevel { mod test { use tari_utilities::epoch_time::EpochTime; - use crate::sharechain::{in_memory::test::new_random_address, p2block::P2Block, p2chain_level::P2ChainLevel}; + use crate::sharechain::{ + in_memory::test::new_random_address, + p2block::P2BlockBuilder, + p2chain_level::P2ChainLevel, + }; #[test] fn it_gets_the_block_chain() { let address = new_random_address(); - let block = P2Block::builder() + let block = P2BlockBuilder::new(None) .with_timestamp(EpochTime::now()) .with_height(0) .with_miner_wallet_address(address.clone()) - .build(); + .build() + .unwrap(); let mut chain_level = P2ChainLevel::new(block.clone()); chain_level.chain_block = block.generate_hash(); @@ -92,14 +97,13 @@ mod test { chain_level.block_in_main_chain().unwrap().generate_hash(), block.generate_hash() ); - - let block_2 = P2Block::builder() + // this is not correct, but we want the hashes to be different from the blocks + let block_2 = P2BlockBuilder::new(Some(&block)) .with_timestamp(EpochTime::now()) .with_height(0) - // this is not correct, but we want the hashes to be different from the blocks - .with_prev_hash(block.generate_hash()) .with_miner_wallet_address(address.clone()) - .build(); + .build() + .unwrap(); chain_level.add_block(block_2.clone()).unwrap(); assert_eq!( @@ -107,13 +111,13 @@ mod test { block.generate_hash() ); - let block_3 = P2Block::builder() + // this is not correct, but we want the hashes to be different from the blocks + let block_3 = P2BlockBuilder::new(Some(&block_2)) .with_timestamp(EpochTime::now()) .with_height(0) - // this is not correct, but we want the hashes to be different from the blocks - .with_prev_hash(block_2.generate_hash()) .with_miner_wallet_address(address) - .build(); + .build() + .unwrap(); chain_level.add_block(block_3.clone()).unwrap(); chain_level.chain_block = block_3.generate_hash();