diff --git a/Cargo.lock b/Cargo.lock index 823c8c7..82a3fa0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1286,12 +1286,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2061,6 +2061,12 @@ dependencies = [ "cc", ] +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + [[package]] name = "ident_case" version = "1.0.1" @@ -2291,9 +2297,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.159" +version = "0.2.168" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" [[package]] name = "liblmdb-sys" @@ -2884,6 +2890,29 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "lmdb-rkv" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447a296f7aca299cfbb50f4e4f3d49451549af655fb7215d7f8c0c3d64bad42b" +dependencies = [ + "bitflags 1.3.2", + "byteorder", + "libc", + "lmdb-rkv-sys", +] + +[[package]] +name = "lmdb-rkv-sys" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61b9ce6b3be08acefa3003c57b7565377432a89ec24476bbe72e11d101f852fe" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "lmdb-zero" version = "0.4.4" @@ -3501,6 +3530,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "ordered-float" +version = "3.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1e1c390732d15f1d48471625cd92d154e66db2c56645e29a9cd26f4699f72dc" +dependencies = [ + "num-traits", +] + [[package]] name = "p256" version = "0.13.2" @@ -4279,6 +4317,29 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rkv" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6d906922d99c677624d2042a93f89b2b7df0f6411032237d5d99a602c2487c" +dependencies = [ + "arrayref", + "bincode", + "bitflags 2.6.0", + "byteorder", + "id-arena", + "lazy_static", + "lmdb-rkv", + "log", + "ordered-float 3.9.2", + "paste", + "serde", + "serde_derive", + "thiserror", + "url", + "uuid", +] + [[package]] name = "rtnetlink" version = "0.10.1" @@ -4332,15 +4393,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.37" +version = "0.38.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" dependencies = [ "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -4511,7 +4572,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" dependencies = [ - "ordered-float", + "ordered-float 2.10.1", "serde", ] @@ -4651,6 +4712,7 @@ dependencies = [ "minotari_node_grpc_client", "num", "rand", + "rkv", "serde", "serde_cbor", "serde_json", @@ -4661,6 +4723,7 @@ dependencies = [ "tari_script", "tari_shutdown", "tari_utilities", + "tempfile", "thiserror", "tokio", "tonic", @@ -5429,9 +5492,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.12.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", "fastrand", @@ -5952,6 +6015,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" + [[package]] name = "vcpkg" version = "0.2.15" diff --git a/Cargo.toml b/Cargo.toml index fe98688..b202d4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,6 +62,8 @@ thiserror = "1.0" tokio = { version = "1.41.0", features = ["full"] } tonic = "0.12.3" lru = "0.12.5" +tempfile = "3.14.0" +rkv = { version = "0.19.0", features = ["lmdb"] } [package.metadata.cargo-machete] ignored = ["log4rs"] diff --git a/src/main.rs b/src/main.rs index 205e191..91d9b0b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ use std::{ fs::File, io::Write, panic, + process, time::{SystemTime, UNIX_EPOCH}, }; @@ -56,6 +57,11 @@ async fn main() -> anyhow::Result<()> { let mut file = File::create("panic.log").unwrap(); file.write_all(format!("Panic at {}: {}", location, message).as_bytes()) .unwrap(); + if cfg!(debug_assertions) { + // In debug mode, we want to see the panic message + eprintln!("Panic occurred at {}: {}", location, message); + process::exit(500); + } })); Cli::parse().handle_command(Shutdown::new().to_signal()).await?; diff --git a/src/server/p2p/network.rs b/src/server/p2p/network.rs index 39c05a4..c42517f 100644 --- a/src/server/p2p/network.rs +++ b/src/server/p2p/network.rs @@ -2124,6 +2124,16 @@ where S: ShareChain if num_connections > 20 { continue; } + if num_connections == 0 { + match self.dial_seed_peers().await { + Ok(_) => {}, + Err(e) => { + warn!(target: LOG_TARGET, "Failed to dial seed peers: {e:?}"); + }, + } + continue; + } + let mut num_dialed = 0; let store_read_lock = self.network_peer_store.read().await; // Rather try and search good peers rather than randomly dialing @@ -2131,7 +2141,7 @@ where S: ShareChain for record in store_read_lock.whitelist_peers().values() { // Only dial seed peers if we have 0 connections if !self.swarm.is_connected(&record.peer_id) - && (num_connections == 0 || !store_read_lock.is_seed_peer(&record.peer_id)) { + && !store_read_lock.is_seed_peer(&record.peer_id) { let _unused = self.swarm.dial(record.peer_id); num_dialed += 1; // We can only do 30 connections @@ -2430,6 +2440,13 @@ where S: ShareChain self.query_tx.clone() } + pub async fn dial_seed_peers(&mut self) -> Result<(), Error> { + info!(target: LOG_TARGET, squad = &self.config.squad; "Dialing seed peers..."); + let seed_peers = self.parse_seed_peers().await?; + self.join_seed_peers(seed_peers).await?; + Ok(()) + } + /// Starts p2p service. /// Please note that this is a blocking call! pub async fn start(&mut self) -> Result<(), Error> { @@ -2457,8 +2474,9 @@ where S: ShareChain } self.subscribe_to_topics().await; - let seed_peers = self.parse_seed_peers().await?; - self.join_seed_peers(seed_peers).await?; + self.dial_seed_peers().await?; + // let seed_peers = self.parse_seed_peers().await?; + // self.join_seed_peers(seed_peers).await?; // start initial share chain sync // let in_progress = self.sync_in_progress.clone(); diff --git a/src/sharechain/in_memory.rs b/src/sharechain/in_memory.rs index 8484a59..d27682b 100644 --- a/src/sharechain/in_memory.rs +++ b/src/sharechain/in_memory.rs @@ -21,7 +21,13 @@ use tari_core::{ use tari_utilities::{epoch_time::EpochTime, hex::Hex}; use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; -use super::{MAIN_REWARD_SHARE, MIN_RANDOMX_DIFFICULTY, MIN_SHA3X_DIFFICULTY, UNCLE_REWARD_SHARE}; +use super::{ + lmdb_block_storage::LmdbBlockStorage, + MAIN_REWARD_SHARE, + MIN_RANDOMX_DIFFICULTY, + MIN_SHA3X_DIFFICULTY, + UNCLE_REWARD_SHARE, +}; use crate::{ server::{http::stats_collector::StatsBroadcastClient, Config, PROTOCOL_VERSION}, sharechain::{ @@ -45,7 +51,7 @@ pub const UNCLE_START_HEIGHT: u64 = 10; pub const MAX_MISSING_PARENTS: usize = 10; pub(crate) struct InMemoryShareChain { - p2_chain: Arc>, + p2_chain: Arc>>, pow_algo: PowAlgorithm, block_validation_params: Option>, consensus_manager: ConsensusManager, @@ -73,6 +79,7 @@ impl InMemoryShareChain { config.share_window * 2, config.share_window, config.block_time, + LmdbBlockStorage::new_from_temp_dir(), ))), pow_algo, block_validation_params, @@ -171,7 +178,7 @@ impl InMemoryShareChain { /// Submits a new block to share chain. async fn submit_block_with_lock( &self, - p2_chain: &mut RwLockWriteGuard<'_, P2Chain>, + p2_chain: &mut RwLockWriteGuard<'_, P2Chain>, block: Arc, params: Option>, syncing: bool, @@ -180,8 +187,8 @@ impl InMemoryShareChain { // Check if already added. if let Some(level) = p2_chain.level_at_height(new_block_p2pool_height) { - if level.blocks.contains_key(&block.hash) { - let block_in_chain = level.blocks.get(&block.hash).unwrap(); + if level.contains(&block.hash) { + let block_in_chain = level.get(&block.hash).unwrap(); info!(target: LOG_TARGET, "[{:?}] ✅ Block already added: {}:{}, verified: {}", self.pow_algo, block.height, &block.hash.to_hex()[0..8], block_in_chain.verified); @@ -198,7 +205,7 @@ impl InMemoryShareChain { } // this is safe as we already checked it does exist - let tip_height = p2_chain.get_tip().unwrap().height; + let tip_height = p2_chain.get_tip().unwrap().height(); // We keep more blocks than the share window, but its only to validate the share window. If a block comes in // older than the share window is way too old for us to care about. if block.height < tip_height.saturating_sub(self.config.share_window) && !syncing { @@ -234,7 +241,7 @@ impl InMemoryShareChain { async fn get_calculate_and_cache_hashmap_of_shares( &self, - p2_chain: &mut RwLockWriteGuard<'_, P2Chain>, + p2_chain: &mut RwLockWriteGuard<'_, P2Chain>, ) -> Result)>, ShareChainError> { fn update_insert( miner_shares: &mut HashMap)>, @@ -260,10 +267,9 @@ impl InMemoryShareChain { }; // we want to count 1 short,as the final share will be for this node - let stop_height = tip_level.height.saturating_sub(self.config.share_window - 1); + let stop_height = tip_level.height().saturating_sub(self.config.share_window - 1); let mut cur_block = tip_level - .blocks - .get(&tip_level.chain_block) + .get(&tip_level.chain_block()) .ok_or(ShareChainError::BlockNotFound)?; update_insert( &mut miners_to_shares, @@ -275,7 +281,6 @@ impl InMemoryShareChain { let uncle_block = p2_chain .level_at_height(uncle.0) .ok_or(ShareChainError::UncleBlockNotFound)? - .blocks .get(&uncle.1) .ok_or(ShareChainError::UncleBlockNotFound)?; update_insert( @@ -287,7 +292,7 @@ impl InMemoryShareChain { } while cur_block.height > stop_height { cur_block = p2_chain - .get_parent_block(cur_block) + .get_parent_block(&cur_block) .ok_or(ShareChainError::BlockNotFound)?; update_insert( &mut miners_to_shares, @@ -299,7 +304,6 @@ impl InMemoryShareChain { let uncle_block = p2_chain .level_at_height(uncle.0) .ok_or(ShareChainError::UncleBlockNotFound)? - .blocks .get(&uncle.1) .ok_or(ShareChainError::UncleBlockNotFound)?; update_insert( @@ -316,7 +320,7 @@ impl InMemoryShareChain { fn all_blocks_with_lock( &self, - p2_chain: &RwLockReadGuard<'_, P2Chain>, + p2_chain: &RwLockReadGuard<'_, P2Chain>, start_height: Option, page_size: usize, main_chain_only: bool, @@ -350,7 +354,7 @@ impl InMemoryShareChain { res.push(block.clone()); } } else { - for block in level.blocks.values() { + for block in level.all_blocks() { num_actual_blocks += 1; res.push(block.clone()); } @@ -359,7 +363,7 @@ impl InMemoryShareChain { return Ok(res); } - level = if let Some(new_level) = p2_chain.level_at_height(level.height + 1) { + level = if let Some(new_level) = p2_chain.level_at_height(level.height() + 1) { new_level } else { break; @@ -489,7 +493,7 @@ impl ShareChain for InMemoryShareChain { let bl = self.p2_chain.read().await; let tip_level = bl.get_tip(); if let Some(tip_level) = tip_level { - Ok(Some((tip_level.height, tip_level.chain_block))) + Ok(Some((tip_level.height(), tip_level.chain_block()))) } else { Ok(None) } @@ -545,7 +549,6 @@ impl ShareChain for InMemoryShareChain { let uncle_block = chain_read_lock .level_at_height(uncle.0) .ok_or(ShareChainError::UncleBlockNotFound)? - .blocks .get(&uncle.1) .ok_or(ShareChainError::UncleBlockNotFound)?; miners_to_shares.insert( @@ -589,7 +592,7 @@ impl ShareChain for InMemoryShareChain { // edge case for chain start let prev_block = chain_read_lock.get_tip().and_then(|tip| tip.block_in_main_chain()); let new_height = match prev_block { - Some(prev_block) => prev_block.height.saturating_add(1), + Some(ref prev_block) => prev_block.height.saturating_add(1), None => 0, }; @@ -614,7 +617,7 @@ impl ShareChain for InMemoryShareChain { for uncle in &chain_block.uncles { excluded_uncles.push(uncle.1); } - for block in older_level.blocks.values() { + for block in older_level.all_blocks() { uncles.push(block.clone()); } } @@ -638,7 +641,7 @@ impl ShareChain for InMemoryShareChain { if chain_read_lock .level_at_height(parent.height) .ok_or(ShareChainError::BlockLevelNotFound)? - .chain_block != + .chain_block() != parent.hash { excluded_uncles.push(uncle.hash); @@ -653,7 +656,7 @@ impl ShareChain for InMemoryShareChain { uncles.truncate(UNCLE_LIMIT); } - Ok(P2BlockBuilder::new(prev_block) + Ok(P2BlockBuilder::new(prev_block.as_deref()) .with_timestamp(EpochTime::now()) .with_height(new_height) .with_uncles(&uncles)? @@ -668,7 +671,7 @@ impl ShareChain for InMemoryShareChain { for block in requested_blocks { if let Some(level) = p2_chain_read_lock.level_at_height(block.0) { - if let Some(block) = level.blocks.get(&block.1) { + if let Some(block) = level.get(&block.1) { blocks.push(block.clone()); } else { // if sync requestee only sees their behind on tip, they will fill in fixedhash::zero(), so it wont @@ -707,7 +710,7 @@ impl ShareChain for InMemoryShareChain { for their_block in their_blocks { if let Some(level) = p2_chain_read.level_at_height(their_block.0) { // Only split if the block is in the main chain - if level.chain_block == their_block.1 { + if level.chain_block() == their_block.1 { split_height2 = their_block.0.saturating_add(1); break; } @@ -720,7 +723,7 @@ impl ShareChain for InMemoryShareChain { self.all_blocks_with_lock(&p2_chain_read, Some(cmp::max(split_height, split_height2)), limit, true)?; let tip_level = p2_chain_read .get_tip() - .map(|tip_level| (tip_level.height, tip_level.chain_block)); + .map(|tip_level| (tip_level.height(), tip_level.chain_block())); let chain_pow = p2_chain_read.total_accumulated_tip_difficulty(); Ok((blocks, tip_level, chain_pow)) } @@ -760,7 +763,7 @@ impl ShareChain for InMemoryShareChain { let p2_chain_read_lock = self.p2_chain.read().await; let mut i_have_blocks = Vec::with_capacity(size); if let Some(tip) = p2_chain_read_lock.get_tip() { - let tip_height = tip.height; + let tip_height = tip.height(); let mut height = tip_height; for _ in 0..size { if let Some(level) = p2_chain_read_lock.level_at_height(height) { @@ -864,7 +867,7 @@ pub mod test { for i in 0..15 { let address = new_random_address(); timestamp = timestamp.checked_add(EpochTime::from(10)).unwrap(); - let block = P2BlockBuilder::new(prev_block.as_ref()) + let block = P2BlockBuilder::new(prev_block.as_deref()) .with_timestamp(timestamp) .with_height(i) .with_miner_wallet_address(address.clone()) @@ -918,7 +921,7 @@ pub mod test { for i in 0..15 { let address = miners[i % 5].clone(); timestamp = timestamp.checked_add(EpochTime::from(10)).unwrap(); - let block = P2BlockBuilder::new(prev_block.as_ref()) + let block = P2BlockBuilder::new(prev_block.as_deref()) .with_timestamp(timestamp) .with_height(i as u64) .with_miner_wallet_address(address.clone()) @@ -996,7 +999,7 @@ pub mod test { uncles.push(block.clone()); share_chain.submit_block(block).await.unwrap(); } - let block = P2BlockBuilder::new(prev_block.as_ref()) + let block = P2BlockBuilder::new(prev_block.as_deref()) .with_timestamp(timestamp) .with_height(i as u64) .with_miner_wallet_address(address.clone()) @@ -1041,7 +1044,7 @@ pub mod test { let mut blocks = Vec::new(); let mut prev_block = None; for i in 0..10 { - let block = P2BlockBuilder::new(prev_block.as_ref()) + let block = P2BlockBuilder::new(prev_block.as_deref()) .with_height(i) .with_target_difficulty(Difficulty::from_u64(1).unwrap()) .unwrap() @@ -1085,7 +1088,7 @@ pub mod test { assert_eq!(heights, vec![8, 9]); // Add an extra block in their blocks - let missing_block = P2BlockBuilder::new(prev_block.as_ref()) + let missing_block = P2BlockBuilder::new(prev_block.as_deref()) .with_height(11) .with_target_difficulty(Difficulty::from_u64(10).unwrap()) .unwrap() diff --git a/src/sharechain/lmdb_block_storage.rs b/src/sharechain/lmdb_block_storage.rs new file mode 100644 index 0000000..cd662f4 --- /dev/null +++ b/src/sharechain/lmdb_block_storage.rs @@ -0,0 +1,174 @@ +// Copyright 2024. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +// DAMAGE. + +use std::{ + fs, + sync::{Arc, RwLock}, +}; + +use rkv::{ + backend::{BackendInfo, Lmdb, LmdbEnvironment}, + Manager, + Rkv, + StoreError, + StoreOptions, +}; +use tari_common_types::types::BlockHash; +use tari_utilities::ByteArray; +use tempfile::Builder; + +use super::P2Block; +use crate::server::p2p::messages::{deserialize_message, serialize_message}; + +pub(crate) struct LmdbBlockStorage { + file_handle: Arc>>, +} + +impl LmdbBlockStorage { + pub fn new_from_temp_dir() -> Self { + let root = Builder::new().prefix("p2pool").tempdir().unwrap(); + fs::create_dir_all(root.path()).unwrap(); + let path = root.path(); + let mut manager = Manager::::singleton().write().unwrap(); + let file_handle = manager.get_or_create(path, Rkv::new::).unwrap(); + + Self { file_handle } + } +} + +impl BlockCache for LmdbBlockStorage { + fn get(&self, hash: &BlockHash) -> Option> { + let env = self.file_handle.read().expect("reader"); + let store = env.open_single("block_cache", StoreOptions::create()).unwrap(); + let reader = env.read().expect("reader"); + let block = store.get(&reader, hash.as_bytes()).unwrap(); + if let Some(block) = block { + match block { + rkv::Value::Blob(b) => { + let block = Arc::new(deserialize_message(b).unwrap()); + return Some(block); + }, + _ => { + return None; + }, + } + } + None + } + + fn insert(&self, hash: BlockHash, block: Arc) { + // Retry if the map is full + // This weird pattern of setting a bool is so that the env is closed before resizing, otherwise + // you can't resize with active transactions. + let mut next_resize = false; + + for _retry in 0..10 { + let env = self.file_handle.read().expect("reader"); + if next_resize { + resize_db(&env); + // next_resize = false; + } + let store = env.open_single("block_cache", StoreOptions::create()).unwrap(); + // dbg!(_retry); + let mut writer = env.write().expect("writer"); + let block_blob = serialize_message(&block).unwrap(); + match store.put(&mut writer, hash.as_bytes(), &rkv::Value::Blob(&block_blob)) { + Ok(_) => match writer.commit() { + Ok(_) => { + return; + }, + Err(e) => match e { + StoreError::MapFull => { + next_resize = true; + }, + _ => { + panic!("Error committing block to storage: {:?}", e) + }, + }, + }, + Err(e) => match e { + StoreError::MapFull => { + next_resize = true; + }, + _ => { + panic!("Error committing block to storage: {:?}", e) + }, + }, + } + } + } +} + +fn resize_db(env: &Rkv) { + let size = env.info().map(|i| i.map_size()).unwrap_or(0); + // dbg!(size); + // let new_size = (size as f64 * 1.2f64).ceil() as usize; + let new_size = size * 2; + env.set_map_size(new_size).unwrap(); +} +pub trait BlockCache { + fn get(&self, hash: &BlockHash) -> Option>; + fn insert(&self, hash: BlockHash, block: Arc); + fn contains(&self, hash: &BlockHash) -> bool { + self.get(hash).is_some() + } +} + +#[cfg(test)] +pub mod test { + use std::collections::HashMap; + + use super::*; + + pub(crate) struct InMemoryBlockCache { + blocks: Arc>>>, + } + + impl InMemoryBlockCache { + pub fn new() -> Self { + Self { + blocks: Arc::new(RwLock::new(HashMap::new())), + } + } + } + + impl BlockCache for InMemoryBlockCache { + fn get(&self, hash: &BlockHash) -> Option> { + self.blocks.read().unwrap().get(hash).cloned() + } + + fn insert(&self, hash: BlockHash, block: Arc) { + self.blocks.write().unwrap().insert(hash, block); + } + } + + #[test] + fn test_saving_and_retrieving_blocks() { + let cache = LmdbBlockStorage::new_from_temp_dir(); + let block = Arc::new(P2Block::default()); + let hash = block.hash; + cache.insert(hash, block.clone()); + let retrieved_block = cache.get(&hash).unwrap(); + assert_eq!(block, retrieved_block); + } +} diff --git a/src/sharechain/mod.rs b/src/sharechain/mod.rs index 3322097..19882ce 100644 --- a/src/sharechain/mod.rs +++ b/src/sharechain/mod.rs @@ -47,6 +47,7 @@ pub const MIN_SHA3X_DIFFICULTY: u64 = 100_000_000; // 1 Mhs every ten seconds pub mod error; pub mod in_memory; +pub(crate) mod lmdb_block_storage; pub mod p2block; pub mod p2chain; mod p2chain_level; diff --git a/src/sharechain/p2block.rs b/src/sharechain/p2block.rs index 3a9291c..af4cb3f 100644 --- a/src/sharechain/p2block.rs +++ b/src/sharechain/p2block.rs @@ -155,7 +155,7 @@ pub struct P2BlockBuilder { } impl P2BlockBuilder { - pub fn new(prev_block: Option<&Arc>) -> Self { + pub fn new(prev_block: Option<&P2Block>) -> Self { let mut block = P2Block::default(); match prev_block { Some(prev_block) => { diff --git a/src/sharechain/p2chain.rs b/src/sharechain/p2chain.rs index 6eba14b..a0d6f6d 100644 --- a/src/sharechain/p2chain.rs +++ b/src/sharechain/p2chain.rs @@ -34,6 +34,7 @@ use tari_common_types::types::FixedHash; use tari_core::proof_of_work::{lwma_diff::LinearWeightedMovingAverage, AccumulatedDifficulty}; use tari_utilities::hex::Hex; +use super::lmdb_block_storage::BlockCache; use crate::sharechain::{ error::ShareChainError, in_memory::MAX_UNCLE_AGE, @@ -123,17 +124,18 @@ impl Display for ChainAddResult { } } -pub struct P2Chain { +pub struct P2Chain { pub block_time: u64, + block_cache: Arc, pub cached_shares: Option)>>, - pub(crate) levels: HashMap, + pub(crate) levels: HashMap>, total_size: u64, share_window: u64, current_tip: u64, pub lwma: LinearWeightedMovingAverage, } -impl P2Chain { +impl P2Chain { pub fn total_accumulated_tip_difficulty(&self) -> AccumulatedDifficulty { match self.get_tip() { Some(tip) => tip @@ -153,30 +155,27 @@ impl P2Chain { None } - pub fn level_at_height(&self, height: u64) -> Option<&P2ChainLevel> { + pub fn level_at_height(&self, height: u64) -> Option<&P2ChainLevel> { self.levels.get(&height) } - pub fn get_block_at_height(&self, height: u64, hash: &FixedHash) -> Option<&Arc> { + pub fn get_block_at_height(&self, height: u64, hash: &FixedHash) -> Option> { let level = self.level_at_height(height)?; - level.blocks.get(hash) + level.get(hash) } #[cfg(test)] - fn get_chain_block_at_height(&self, height: u64) -> Option<&Arc> { + fn get_chain_block_at_height(&self, height: u64) -> Option> { let level = self.level_at_height(height)?; - level.blocks.get(&level.chain_block) + level.get(&level.chain_block()) } - pub fn level_at_height_mut(&mut self, height: u64) -> Option<&mut P2ChainLevel> { - self.levels.get_mut(&height) - } - - pub fn new_empty(total_size: u64, share_window: u64, block_time: u64) -> Self { + pub fn new_empty(total_size: u64, share_window: u64, block_time: u64, block_cache: T) -> Self { let levels = HashMap::new(); let lwma = LinearWeightedMovingAverage::new(DIFFICULTY_ADJUSTMENT_WINDOW, block_time).expect("Failed to create LWMA"); Self { + block_cache: Arc::new(block_cache), block_time, cached_shares: None, levels, @@ -209,10 +208,10 @@ impl P2Chain { // the newly added block == 0 self.lwma.add_back(block.timestamp, block.target_difficulty()); let level = self - .level_at_height_mut(new_height) + .level_at_height(new_height) .ok_or(ShareChainError::BlockLevelNotFound)?; - level.chain_block = hash; - self.current_tip = level.height; + level.set_chain_block(hash); + self.current_tip = level.height(); self.cleanup_chain() } @@ -274,7 +273,7 @@ impl P2Chain { // now lets check the uncles 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() { + if self.get_parent_block(&uncle_block).is_none() { new_tip .missing_blocks .insert(uncle_block.prev_hash, uncle_block.height.saturating_sub(1)); @@ -307,7 +306,7 @@ impl P2Chain { return Ok((new_tip, Vec::new())); } - if self.get_tip().is_some() && self.get_tip().unwrap().chain_block == block.prev_hash { + 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 { @@ -315,18 +314,18 @@ impl P2Chain { .get_block_at_height(uncle.0, &uncle.1) .ok_or(ShareChainError::BlockNotFound)?; let uncle_parent = self - .get_parent_block(uncle_block) + .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 { + 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 { + if own_level.chain_block() == uncle.1 { return Err(ShareChainError::UncleInMainChain { height: uncle.0, hash: uncle.1, @@ -352,7 +351,7 @@ impl P2Chain { all_blocks_verified = false; // 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() { + if self.get_parent_block(&parent).is_none() { new_tip .missing_blocks .insert(parent.prev_hash, parent.height.saturating_sub(1)); @@ -379,7 +378,7 @@ impl P2Chain { let level = self .level_at_height(current_counting_block.height) .ok_or(ShareChainError::BlockLevelNotFound)?; - if level.chain_block == current_counting_block.hash { + if level.chain_block() == current_counting_block.hash { break; } // we can unwrap as we now the parent exists @@ -403,17 +402,17 @@ impl P2Chain { .expect("Failed to create LWMA"); self.lwma.add_front(block.timestamp, block.target_difficulty()); let chain_height = self - .level_at_height_mut(block.height) + .level_at_height(block.height) .ok_or(ShareChainError::BlockLevelNotFound)?; - chain_height.chain_block = block.hash; + chain_height.set_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(); + let mut_child_level = self.level_at_height(current_height.saturating_add(1)).unwrap(); + mut_child_level.set_chain_block(FixedHash::zero()); current_height += 1; } @@ -421,30 +420,28 @@ impl P2Chain { let mut counter = 0; while self.level_at_height(current_block.height.saturating_sub(1)).is_some() { counter += 1; - let parent_level = (self.level_at_height(current_block.height.saturating_sub(1)).unwrap()).clone(); - if current_block.prev_hash != parent_level.chain_block { + let parent_level = self.level_at_height(current_block.height.saturating_sub(1)).unwrap(); + if current_block.prev_hash != parent_level.chain_block() { // safety check - let nextblock = parent_level.blocks.get(¤t_block.prev_hash); + let nextblock = parent_level.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. current_block: {:?}, current tip: {:?}", current_block, - self.get_tip() + self.get_tip().map(|t| t.height()) ); } // 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; + let mut_parent_level = self.level_at_height(current_block.height.saturating_sub(1)).unwrap(); + mut_parent_level.set_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); + let nextblock = parent_level.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!( @@ -452,7 +449,7 @@ impl P2Chain { parent block was not found and chain data is corrupted. current_block: {:?}, current \ tip: {:?}", current_block, - self.get_tip() + self.get_tip().map(|t| t.height()) ); } @@ -486,14 +483,14 @@ impl P2Chain { // let see if we already have a block is a missing block of some other block for check_height in (height + 1)..height + MAX_UNCLE_AGE { if let Some(level) = self.level_at_height(check_height) { - for block in &level.blocks { - for uncles in &block.1.uncles { + for block in &level.all_blocks() { + for uncles in &block.uncles { if uncles.1 == hash { - next_level_data.push((block.1.height, block.1.hash)); + next_level_data.push((block.height, block.hash)); } } - if block.1.prev_hash == hash { - next_level_data.push((block.1.height, block.1.hash)); + if block.prev_hash == hash { + next_level_data.push((block.height, block.hash)); } } } @@ -506,7 +503,7 @@ impl P2Chain { let level = self .level_at_height(height) .ok_or(ShareChainError::BlockLevelNotFound)?; - let block = level.blocks.get(&hash).ok_or(ShareChainError::BlockNotFound)?; + let block = level.get(&hash).ok_or(ShareChainError::BlockNotFound)?; if block.verified { return Ok(()); } @@ -533,9 +530,9 @@ impl P2Chain { // lets replace this actual_block.verified = verified; let level = self - .level_at_height_mut(height) + .level_at_height(height) .ok_or(ShareChainError::BlockLevelNotFound)?; - level.blocks.insert(hash, Arc::new(actual_block)); + level.add_block(Arc::new(actual_block))?; return Ok(()); } @@ -552,9 +549,9 @@ impl P2Chain { // lets replace this actual_block.verified = verified; let level = self - .level_at_height_mut(height) + .level_at_height(height) .ok_or(ShareChainError::BlockLevelNotFound)?; - level.blocks.insert(hash, Arc::new(actual_block)); + level.add_block(Arc::new(actual_block))?; } Ok(()) @@ -565,18 +562,18 @@ impl P2Chain { let block_hash = block.hash; // edge case no current chain, lets just add if self.levels.is_empty() { - let new_level = P2ChainLevel::new(block); + let new_level = P2ChainLevel::new(block, self.block_cache.clone()); self.levels.insert(new_block_height, new_level); return self.verify_chain(new_block_height, block_hash); } - match self.level_at_height_mut(new_block_height) { + match self.level_at_height(new_block_height) { Some(level) => { level.add_block(block)?; self.verify_chain(new_block_height, block_hash) }, None => { let height = block.height; - let level = P2ChainLevel::new(block); + let level = P2ChainLevel::new(block, self.block_cache.clone()); self.levels.insert(height, level); self.verify_chain(new_block_height, block_hash) }, @@ -594,7 +591,7 @@ impl P2Chain { self.add_block_inner(block) } - pub fn get_parent_block(&self, block: &P2Block) -> Option<&Arc> { + pub fn get_parent_block(&self, block: &P2Block) -> Option> { let parent_height = match block.height.checked_sub(1) { Some(height) => height, None => return None, @@ -603,16 +600,16 @@ impl P2Chain { Some(level) => level, None => return None, }; - parent_level.blocks.get(&block.prev_hash) + parent_level.get(&block.prev_hash) } - pub fn get_tip(&self) -> Option<&P2ChainLevel> { + pub fn get_tip(&self) -> Option<&P2ChainLevel> { self.level_at_height(self.current_tip) - .filter(|&level| level.chain_block != FixedHash::zero()) + .filter(|&level| level.chain_block() != FixedHash::zero()) } pub fn get_height(&self) -> u64 { - self.get_tip().map(|tip| tip.height).unwrap_or(0) + self.get_tip().map(|tip| tip.height()).unwrap_or(0) } pub fn get_max_chain_length(&self) -> usize { @@ -665,17 +662,21 @@ mod test { use tari_utilities::epoch_time::EpochTime; use super::*; - use crate::sharechain::{in_memory::test::new_random_address, p2block::P2BlockBuilder}; + use crate::sharechain::{ + in_memory::test::new_random_address, + lmdb_block_storage::LmdbBlockStorage, + p2block::P2BlockBuilder, + }; #[test] fn test_only_keeps_size() { - let mut chain = P2Chain::new_empty(10, 5, 10); + let mut chain = P2Chain::new_empty(10, 5, 10, LmdbBlockStorage::new_from_temp_dir()); let mut tari_block = Block::new(BlockHeader::new(0), AggregateBody::empty()); let mut prev_block = None; for i in 0..2100 { tari_block.header.nonce = i; let address = new_random_address(); - let block = P2BlockBuilder::new(prev_block.as_ref()) + let block = P2BlockBuilder::new(prev_block.as_deref()) .with_timestamp(EpochTime::now()) .with_height(i) .with_miner_wallet_address(address.clone()) @@ -697,14 +698,14 @@ mod test { #[test] fn get_tips() { - let mut chain = P2Chain::new_empty(10, 5, 10); + let mut chain = P2Chain::new_empty(10, 5, 10, LmdbBlockStorage::new_from_temp_dir()); 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 = P2BlockBuilder::new(prev_block.as_ref()) + let block = P2BlockBuilder::new(prev_block.as_deref()) .with_timestamp(EpochTime::now()) .with_height(i) .with_tari_block(tari_block.clone()) @@ -716,7 +717,7 @@ mod test { chain.add_block_to_chain(block.clone()).unwrap(); let level = chain.get_tip().unwrap(); - assert_eq!(level.height, i); + assert_eq!(level.height(), i); assert_eq!(level.block_in_main_chain().unwrap().original_header.nonce, i); } } @@ -725,14 +726,14 @@ mod 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, 10); + let mut chain = P2Chain::new_empty(10, 5, 10, LmdbBlockStorage::new_from_temp_dir()); let mut prev_block = None; let mut tari_block = Block::new(BlockHeader::new(0), AggregateBody::empty()); for i in 1..6 { tari_block.header.nonce = i; let address = new_random_address(); - let block = P2BlockBuilder::new(prev_block.as_ref()) + let block = P2BlockBuilder::new(prev_block.as_deref()) .with_timestamp(EpochTime::now()) .with_height(i) .with_tari_block(tari_block.clone()) @@ -746,7 +747,7 @@ mod test { } tari_block.header.nonce = 6; let address = new_random_address(); - let block = P2BlockBuilder::new(prev_block.as_ref()) + let block = P2BlockBuilder::new(prev_block.as_deref()) .with_timestamp(EpochTime::now()) .with_height(6) .with_tari_block(tari_block.clone()) @@ -757,7 +758,7 @@ mod test { chain.add_block_to_chain(block.clone()).unwrap(); let level = chain.get_tip().unwrap(); - assert_eq!(level.height, 6); + assert_eq!(level.height(), 6); // the whole chain must be verified chain.assert_share_window_verified(); @@ -771,7 +772,7 @@ 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, 10); + let mut chain = P2Chain::new_empty(10, 5, 10, LmdbBlockStorage::new_from_temp_dir()); let mut prev_block = None; let mut tari_block = Block::new(BlockHeader::new(0), AggregateBody::empty()); @@ -779,7 +780,7 @@ mod test { for i in 0..7 { tari_block.header.nonce = i; let address = new_random_address(); - let block = P2BlockBuilder::new(prev_block.as_ref()) + let block = P2BlockBuilder::new(prev_block.as_deref()) .with_timestamp(EpochTime::now()) .with_height(i) .with_tari_block(tari_block.clone()) @@ -794,7 +795,7 @@ mod test { assert!(chain.get_tip().is_none()); assert_eq!(chain.current_tip, 0); assert_eq!(chain.levels.len(), 1); - assert_eq!(chain.levels[&6].height, 6); + assert_eq!(chain.levels[&6].height(), 6); for i in (2..6).rev() { chain.add_block_to_chain(blocks[i].clone()).unwrap(); @@ -804,7 +805,7 @@ mod test { chain.add_block_to_chain(blocks[1].clone()).unwrap(); let level = chain.get_tip().unwrap(); - assert_eq!(level.height, 6); + assert_eq!(level.height(), 6); chain.assert_share_window_verified(); } @@ -814,7 +815,7 @@ 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(20, 10, 10); + let mut chain = P2Chain::new_empty(20, 10, 10, LmdbBlockStorage::new_from_temp_dir()); let mut prev_block = None; let mut tari_block = Block::new(BlockHeader::new(0), AggregateBody::empty()); @@ -822,7 +823,7 @@ mod test { for i in 0..20 { tari_block.header.nonce = i; let address = new_random_address(); - let block = P2BlockBuilder::new(prev_block.as_ref()) + let block = P2BlockBuilder::new(prev_block.as_deref()) .with_timestamp(EpochTime::now()) .with_height(i) .with_tari_block(tari_block.clone()) @@ -835,16 +836,16 @@ mod test { } for i in 0..9 { chain.add_block_to_chain(blocks[i].clone()).unwrap(); - assert_eq!(chain.get_tip().unwrap().height, i as u64); + assert_eq!(chain.get_tip().unwrap().height(), i as u64); chain.add_block_to_chain(blocks[19 - i].clone()).unwrap(); - assert_eq!(chain.get_tip().unwrap().height, i as u64); + assert_eq!(chain.get_tip().unwrap().height(), i as u64); } chain.add_block_to_chain(blocks[9].clone()).unwrap(); - assert_eq!(chain.get_tip().unwrap().height, 9); + assert_eq!(chain.get_tip().unwrap().height(), 9); chain.add_block_to_chain(blocks[10].clone()).unwrap(); - assert_eq!(chain.get_tip().unwrap().height, 19); + assert_eq!(chain.get_tip().unwrap().height(), 19); chain.assert_share_window_verified(); } @@ -855,7 +856,7 @@ 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, 10); + let mut chain = P2Chain::new_empty(10, 5, 10, LmdbBlockStorage::new_from_temp_dir()); let mut prev_block = None; let mut tari_block = Block::new(BlockHeader::new(0), AggregateBody::empty()); @@ -863,7 +864,7 @@ mod test { for i in 0..6 { tari_block.header.nonce = i; let address = new_random_address(); - let block = P2BlockBuilder::new(prev_block.as_ref()) + let block = P2BlockBuilder::new(prev_block.as_deref()) .with_timestamp(EpochTime::now()) .with_height(i) .with_tari_block(tari_block.clone()) @@ -887,7 +888,7 @@ mod test { tari_block.header.nonce = 6; let address = new_random_address(); - let block = P2BlockBuilder::new(prev_block.as_ref()) + let block = P2BlockBuilder::new(prev_block.as_deref()) .with_timestamp(EpochTime::now()) .with_height(6) .with_tari_block(tari_block.clone()) @@ -903,7 +904,7 @@ mod test { assert!(chain.get_tip().is_none()); assert_eq!(chain.current_tip, 0); assert_eq!(chain.levels.len(), 1); - assert_eq!(chain.levels[&6].height, 6); + assert_eq!(chain.levels[&6].height(), 6); for i in (2..6).rev() { chain.add_block_to_chain(blocks[i].clone()).unwrap(); @@ -917,19 +918,19 @@ mod test { chain.add_block_to_chain(uncle_block).unwrap(); let level = chain.get_tip().unwrap(); - assert_eq!(level.height, 6); + assert_eq!(level.height(), 6); } #[test] fn get_parent() { - let mut chain = P2Chain::new_empty(10, 5, 10); + let mut chain = P2Chain::new_empty(10, 5, 10, LmdbBlockStorage::new_from_temp_dir()); let mut prev_block = None; let mut tari_block = Block::new(BlockHeader::new(0), AggregateBody::empty()); for i in 0..2100 { tari_block.header.nonce = i; let address = new_random_address(); - let block = P2BlockBuilder::new(prev_block.as_ref()) + let block = P2BlockBuilder::new(prev_block.as_deref()) .with_timestamp(EpochTime::now()) .with_height(i) .with_miner_wallet_address(address.clone()) @@ -944,7 +945,7 @@ mod test { let level = chain.level_at_height(i).unwrap(); let block = level.block_in_main_chain().unwrap(); if i > 0 { - let parent = chain.get_parent_block(block).unwrap(); + let parent = chain.get_parent_block(&block).unwrap(); assert_eq!(parent.original_header.nonce, i - 1); } } @@ -956,14 +957,14 @@ mod test { #[test] fn test_dont_set_tip_on_single_high_height() { - let mut chain = P2Chain::new_empty(10, 5, 10); + let mut chain = P2Chain::new_empty(10, 5, 10, LmdbBlockStorage::new_from_temp_dir()); let mut prev_block = None; let mut tari_block = Block::new(BlockHeader::new(0), AggregateBody::empty()); for i in 0..20 { tari_block.header.nonce = i; let address = new_random_address(); - let block = P2BlockBuilder::new(prev_block.as_ref()) + let block = P2BlockBuilder::new(prev_block.as_deref()) .with_timestamp(EpochTime::now()) .with_height(i) .with_tari_block(tari_block.clone()) @@ -975,11 +976,11 @@ mod test { chain.add_block_to_chain(block.clone()).unwrap(); let level = chain.get_tip().unwrap(); - assert_eq!(level.height, i); + assert_eq!(level.height(), i); } // we do this so we can add a missing parent or 2 let address = new_random_address(); - let block = P2BlockBuilder::new(prev_block.as_ref()) + let block = P2BlockBuilder::new(prev_block.as_deref()) .with_timestamp(EpochTime::now()) .with_height(100) .with_tari_block(tari_block.clone()) @@ -989,7 +990,7 @@ mod test { .unwrap(); prev_block = Some(block.clone()); let address = new_random_address(); - let block = P2BlockBuilder::new(prev_block.as_ref()) + let block = P2BlockBuilder::new(prev_block.as_deref()) .with_timestamp(EpochTime::now()) .with_height(2000) .with_tari_block(tari_block.clone()) @@ -1002,10 +1003,10 @@ mod test { chain.add_block_to_chain(block.clone()).unwrap(); let level = chain.get_tip().unwrap(); - assert_eq!(level.height, 19); + assert_eq!(level.height(), 19); let address = new_random_address(); - let block = P2BlockBuilder::new(prev_block.as_ref()) + let block = P2BlockBuilder::new(prev_block.as_deref()) .with_timestamp(EpochTime::now()) .with_height(1000) .with_tari_block(tari_block.clone()) @@ -1015,7 +1016,7 @@ mod test { .unwrap(); prev_block = Some(block.clone()); let address = new_random_address(); - let block = P2BlockBuilder::new(prev_block.as_ref()) + let block = P2BlockBuilder::new(prev_block.as_deref()) .with_timestamp(EpochTime::now()) .with_height(20000) .with_tari_block(tari_block.clone()) @@ -1037,7 +1038,7 @@ mod test { #[test] fn add_blocks_to_chain_happy_path() { - let mut chain = P2Chain::new_empty(10, 5, 10); + let mut chain = P2Chain::new_empty(10, 5, 10, LmdbBlockStorage::new_from_temp_dir()); let mut timestamp = EpochTime::now(); let mut prev_block = None; @@ -1045,7 +1046,7 @@ mod test { for i in 0..32 { let address = new_random_address(); timestamp = timestamp.checked_add(EpochTime::from(10)).unwrap(); - let block = P2BlockBuilder::new(prev_block.as_ref()) + let block = P2BlockBuilder::new(prev_block.as_deref()) .with_timestamp(timestamp) .with_height(i) .with_miner_wallet_address(address.clone()) @@ -1068,7 +1069,7 @@ mod test { #[test] fn add_blocks_to_chain_small_reorg() { - let mut chain = P2Chain::new_empty(10, 5, 10); + let mut chain = P2Chain::new_empty(10, 5, 10, LmdbBlockStorage::new_from_temp_dir()); let mut timestamp = EpochTime::now(); let mut prev_block = None; @@ -1078,7 +1079,7 @@ mod test { tari_block.header.nonce = i; let address = new_random_address(); timestamp = timestamp.checked_add(EpochTime::from(10)).unwrap(); - let block = P2BlockBuilder::new(prev_block.as_ref()) + let block = P2BlockBuilder::new(prev_block.as_deref()) .with_timestamp(timestamp) .with_height(i) .with_miner_wallet_address(address.clone()) @@ -1106,13 +1107,13 @@ mod test { ); let block_29 = chain.level_at_height(29).unwrap().block_in_main_chain().unwrap(); - prev_block = Some((*block_29).clone()); + prev_block = Some(Arc::new((*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 = P2BlockBuilder::new(prev_block.as_ref()) + let block = P2BlockBuilder::new(prev_block.as_deref()) .with_timestamp(timestamp) .with_height(30) .with_miner_wallet_address(address.clone()) @@ -1134,7 +1135,7 @@ mod test { tari_block.header.nonce = 31 * 2; timestamp = timestamp.checked_add(EpochTime::from(10)).unwrap(); - let block = P2BlockBuilder::new(prev_block.as_ref()) + let block = P2BlockBuilder::new(prev_block.as_deref()) .with_timestamp(timestamp) .with_height(31) .with_miner_wallet_address(address.clone()) @@ -1164,14 +1165,14 @@ mod test { #[test] fn add_blocks_to_chain_super_large_reorg() { // this test will verify that we reorg to a completely new chain - let mut chain = P2Chain::new_empty(10, 5, 20); + let mut chain = P2Chain::new_empty(10, 5, 20, LmdbBlockStorage::new_from_temp_dir()); let mut prev_block = None; let mut tari_block = Block::new(BlockHeader::new(0), AggregateBody::empty()); for i in 0..1000 { tari_block.header.nonce = i; let address = new_random_address(); - let block = P2BlockBuilder::new(prev_block.as_ref()) + let block = P2BlockBuilder::new(prev_block.as_deref()) .with_timestamp(EpochTime::now()) .with_height(i) .with_tari_block(tari_block.clone()) @@ -1186,14 +1187,14 @@ mod test { } assert_eq!(chain.current_tip, 999); - assert_eq!(chain.get_tip().unwrap().chain_block, prev_block.unwrap().hash); + assert_eq!(chain.get_tip().unwrap().chain_block(), prev_block.unwrap().hash); let mut prev_block = None; let mut tari_block = Block::new(BlockHeader::new(0), AggregateBody::empty()); for i in 0..1000 { tari_block.header.nonce = i + 100; let address = new_random_address(); - let block = P2BlockBuilder::new(prev_block.as_ref()) + let block = P2BlockBuilder::new(prev_block.as_deref()) .with_timestamp(EpochTime::now()) .with_height(i) .with_tari_block(tari_block.clone()) @@ -1207,7 +1208,7 @@ mod test { chain.add_block_to_chain(block).unwrap(); } assert_eq!(chain.current_tip, 999); - assert_eq!(chain.get_tip().unwrap().chain_block, prev_block.unwrap().hash); + assert_eq!(chain.get_tip().unwrap().chain_block(), prev_block.unwrap().hash); assert_eq!( chain .get_tip() @@ -1225,7 +1226,7 @@ mod test { #[test] fn add_blocks_missing_block() { // this test will verify that we reorg to a completely new chain - let mut chain = P2Chain::new_empty(50, 25, 20); + let mut chain = P2Chain::new_empty(50, 25, 20, LmdbBlockStorage::new_from_temp_dir()); let mut prev_block = None; let mut tari_block = Block::new(BlockHeader::new(0), AggregateBody::empty()); @@ -1233,7 +1234,7 @@ mod test { for i in 0..50 { tari_block.header.nonce = i; let address = new_random_address(); - let block = P2BlockBuilder::new(prev_block.as_ref()) + let block = P2BlockBuilder::new(prev_block.as_deref()) .with_timestamp(EpochTime::now()) .with_height(i) .with_tari_block(tari_block.clone()) @@ -1256,7 +1257,7 @@ mod test { chain.add_block_to_chain(blocks[25].clone()).unwrap(); assert_eq!(chain.current_tip, 49); - assert_eq!(chain.get_tip().unwrap().chain_block, prev_block.unwrap().hash); + assert_eq!(chain.get_tip().unwrap().chain_block(), prev_block.unwrap().hash); chain.assert_share_window_verified(); } @@ -1264,14 +1265,14 @@ mod test { #[test] fn reorg_with_missing_uncle() { // this test will verify that we reorg to a completely new chain - let mut chain = P2Chain::new_empty(50, 25, 20); + let mut chain = P2Chain::new_empty(50, 25, 20, LmdbBlockStorage::new_from_temp_dir()); let mut prev_block = None; let mut tari_block = Block::new(BlockHeader::new(0), AggregateBody::empty()); for i in 0..50 { tari_block.header.nonce = i; let address = new_random_address(); - let block = P2BlockBuilder::new(prev_block.as_ref()) + let block = P2BlockBuilder::new(prev_block.as_deref()) .with_timestamp(EpochTime::now()) .with_height(i) .with_tari_block(tari_block.clone()) @@ -1286,7 +1287,7 @@ mod test { } assert_eq!(chain.current_tip, 49); - assert_eq!(chain.get_tip().unwrap().chain_block, prev_block.unwrap().hash); + assert_eq!(chain.get_tip().unwrap().chain_block(), prev_block.unwrap().hash); let mut prev_block = None; let mut tari_block = Block::new(BlockHeader::new(0), AggregateBody::empty()); @@ -1296,7 +1297,7 @@ mod test { tari_block.header.nonce = i + 100; let address = new_random_address(); let uncles = if i == 25 { - let uncle = P2BlockBuilder::new(uncle_parent.as_ref()) + let uncle = P2BlockBuilder::new(uncle_parent.as_deref()) .with_timestamp(EpochTime::now()) .with_height(24) .with_tari_block(tari_block.clone()) @@ -1309,7 +1310,7 @@ mod test { } else { vec![] }; - let block = P2BlockBuilder::new(prev_block.as_ref()) + let block = P2BlockBuilder::new(prev_block.as_deref()) .with_timestamp(EpochTime::now()) .with_height(i) .with_tari_block(tari_block.clone()) @@ -1330,9 +1331,9 @@ mod test { assert_eq!(chain.current_tip, 49); let hash = prev_block.unwrap().hash; - assert_ne!(chain.get_tip().unwrap().chain_block, hash); + assert_ne!(chain.get_tip().unwrap().chain_block(), hash); chain.add_block_to_chain(uncle_block.unwrap()).unwrap(); - assert_eq!(chain.get_tip().unwrap().chain_block, hash); + assert_eq!(chain.get_tip().unwrap().chain_block(), hash); assert_eq!( chain .get_tip() @@ -1350,14 +1351,14 @@ mod test { #[test] fn add_blocks_to_chain_super_large_reorg_only_window() { // this test will verify that we reorg to a completely new chain - let mut chain = P2Chain::new_empty(10, 5, 20); + let mut chain = P2Chain::new_empty(10, 5, 20, LmdbBlockStorage::new_from_temp_dir()); let mut prev_block = None; let mut tari_block = Block::new(BlockHeader::new(0), AggregateBody::empty()); for i in 0..1000 { tari_block.header.nonce = i; let address = new_random_address(); - let block = P2BlockBuilder::new(prev_block.as_ref()) + let block = P2BlockBuilder::new(prev_block.as_deref()) .with_timestamp(EpochTime::now()) .with_height(i) .with_tari_block(tari_block.clone()) @@ -1372,7 +1373,7 @@ mod test { } assert_eq!(chain.current_tip, 999); - assert_eq!(chain.get_tip().unwrap().chain_block, prev_block.unwrap().hash); + assert_eq!(chain.get_tip().unwrap().chain_block(), prev_block.unwrap().hash); let mut prev_block = None; let mut tari_block = Block::new(BlockHeader::new(0), AggregateBody::empty()); @@ -1380,7 +1381,7 @@ mod test { for i in 0..1000 { tari_block.header.nonce = i + 100; let address = new_random_address(); - let block = P2BlockBuilder::new(prev_block.as_ref()) + let block = P2BlockBuilder::new(prev_block.as_deref()) .with_timestamp(EpochTime::now()) .with_height(i) .with_tari_block(tari_block.clone()) @@ -1397,7 +1398,7 @@ mod test { chain.add_block_to_chain(block.clone()).unwrap(); } assert_eq!(chain.current_tip, 999); - assert_eq!(chain.get_tip().unwrap().chain_block, prev_block.unwrap().hash); + assert_eq!(chain.get_tip().unwrap().chain_block(), prev_block.unwrap().hash); assert_eq!( chain .get_tip() @@ -1414,7 +1415,7 @@ mod test { #[test] fn calculate_total_difficulty_correctly() { - let mut chain = P2Chain::new_empty(10, 5, 10); + let mut chain = P2Chain::new_empty(10, 5, 10, LmdbBlockStorage::new_from_temp_dir()); let mut timestamp = EpochTime::now(); let mut prev_block = None; @@ -1422,7 +1423,7 @@ mod test { for i in 1..15 { let address = new_random_address(); timestamp = timestamp.checked_add(EpochTime::from(10)).unwrap(); - let block = P2BlockBuilder::new(prev_block.as_ref()) + let block = P2BlockBuilder::new(prev_block.as_deref()) .with_timestamp(timestamp) .with_height(i) .with_miner_wallet_address(address.clone()) @@ -1443,7 +1444,7 @@ mod test { #[test] fn calculate_total_difficulty_correctly_with_uncles() { - let mut chain = P2Chain::new_empty(10, 5, 10); + let mut chain = P2Chain::new_empty(10, 5, 10, LmdbBlockStorage::new_from_temp_dir()); let mut timestamp = EpochTime::now(); let mut prev_block = None; @@ -1455,7 +1456,7 @@ mod test { 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)) + let block = P2BlockBuilder::new(Some(&prev_uncle)) .with_timestamp(timestamp) .with_height(i - 1) .with_miner_wallet_address(address.clone()) @@ -1466,7 +1467,7 @@ mod test { uncles.push(block.clone()); chain.add_block_to_chain(block).unwrap(); } - let block = P2BlockBuilder::new(prev_block.as_ref()) + let block = P2BlockBuilder::new(prev_block.as_deref()) .with_timestamp(timestamp) .with_height(i) .with_miner_wallet_address(address.clone()) @@ -1495,7 +1496,7 @@ mod test { #[test] fn calculate_total_difficulty_correctly_with_wrapping_blocks() { - let mut chain = P2Chain::new_empty(10, 5, 10); + let mut chain = P2Chain::new_empty(10, 5, 10, LmdbBlockStorage::new_from_temp_dir()); let mut timestamp = EpochTime::now(); let mut prev_block = None; @@ -1507,7 +1508,7 @@ mod test { 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)) + let block = P2BlockBuilder::new(Some(&prev_uncle)) .with_timestamp(timestamp) .with_height(i - 1) .with_miner_wallet_address(address.clone()) @@ -1518,7 +1519,7 @@ mod test { uncles.push(block.clone()); chain.add_block_to_chain(block).unwrap(); } - let block = P2BlockBuilder::new(prev_block.as_ref()) + let block = P2BlockBuilder::new(prev_block.as_deref()) .with_timestamp(timestamp) .with_height(i) .with_miner_wallet_address(address.clone()) @@ -1547,7 +1548,7 @@ mod test { #[test] fn reorg_with_uncles() { - let mut chain = P2Chain::new_empty(10, 5, 10); + let mut chain = P2Chain::new_empty(10, 5, 10, LmdbBlockStorage::new_from_temp_dir()); let mut timestamp = EpochTime::now(); let mut prev_block = None; @@ -1559,7 +1560,7 @@ mod test { 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)) + let block = P2BlockBuilder::new(Some(&prev_uncle)) .with_timestamp(timestamp) .with_height(i - 1) .with_miner_wallet_address(address.clone()) @@ -1570,7 +1571,7 @@ mod test { uncles.push(block.clone()); chain.add_block_to_chain(block).unwrap(); } - let block = P2BlockBuilder::new(prev_block.as_ref()) + let block = P2BlockBuilder::new(prev_block.as_deref()) .with_timestamp(timestamp) .with_height(i) .with_miner_wallet_address(address.clone()) @@ -1591,7 +1592,7 @@ mod test { let mut uncles = Vec::new(); let prev_uncle = chain.level_at_height(6).unwrap().block_in_main_chain().unwrap(); // lets create an uncle block - let block = P2BlockBuilder::new(Some(prev_uncle)) + let block = P2BlockBuilder::new(Some(&prev_uncle)) .with_timestamp(timestamp) .with_height(7) .with_miner_wallet_address(address.clone()) @@ -1601,8 +1602,10 @@ mod test { .unwrap(); uncles.push(block.clone()); chain.add_block_to_chain(block).unwrap(); - prev_block = Some((*chain.level_at_height(7).unwrap().block_in_main_chain().unwrap()).clone()); - let block = P2BlockBuilder::new(prev_block.as_ref()) + prev_block = Some(Arc::new( + (*chain.level_at_height(7).unwrap().block_in_main_chain().unwrap()).clone(), + )); + let block = P2BlockBuilder::new(prev_block.as_deref()) .with_timestamp(timestamp) .with_height(8) .with_miner_wallet_address(address.clone()) @@ -1617,7 +1620,7 @@ mod test { chain.add_block_to_chain(block).unwrap(); // lets create an uncle block let mut uncles = Vec::new(); - let block = P2BlockBuilder::new(prev_block.as_ref()) + let block = P2BlockBuilder::new(prev_block.as_deref()) .with_timestamp(timestamp) .with_height(8) .with_miner_wallet_address(address.clone()) @@ -1653,14 +1656,14 @@ mod test { #[test] fn rerog_less_than_share_window() { - let mut chain = P2Chain::new_empty(20, 15, 20); + let mut chain = P2Chain::new_empty(20, 15, 20, LmdbBlockStorage::new_from_temp_dir()); 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 = P2BlockBuilder::new(prev_block.as_ref()) + let block = P2BlockBuilder::new(prev_block.as_deref()) .with_timestamp(EpochTime::now()) .with_height(i) .with_tari_block(tari_block.clone()) @@ -1674,7 +1677,7 @@ mod test { chain.add_block_to_chain(block.clone()).unwrap(); let level = chain.get_tip().unwrap(); - assert_eq!(level.height, i); + assert_eq!(level.height(), i); assert_eq!(level.block_in_main_chain().unwrap().original_header.nonce, i); } @@ -1686,7 +1689,7 @@ mod test { for i in 0..10 { tari_block.header.nonce = i + 100; let address = new_random_address(); - let block = P2BlockBuilder::new(prev_block.as_ref()) + let block = P2BlockBuilder::new(prev_block.as_deref()) .with_timestamp(EpochTime::now()) .with_height(i) .with_tari_block(tari_block.clone()) @@ -1701,7 +1704,7 @@ mod test { let level = chain.get_tip().unwrap(); - assert_eq!(level.height, 9); + assert_eq!(level.height(), 9); if i < 9 { // less than 9 it has not reorged yet assert_eq!(level.block_in_main_chain().unwrap().original_header.nonce, 9); @@ -1715,14 +1718,14 @@ mod test { #[test] fn rests_levels_after_reorg() { - let mut chain = P2Chain::new_empty(20, 15, 20); + let mut chain = P2Chain::new_empty(20, 15, 20, LmdbBlockStorage::new_from_temp_dir()); 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 = P2BlockBuilder::new(prev_block.as_ref()) + let block = P2BlockBuilder::new(prev_block.as_deref()) .with_timestamp(EpochTime::now()) .with_height(i) .with_tari_block(tari_block.clone()) @@ -1736,13 +1739,16 @@ mod test { chain.add_block_to_chain(block.clone()).unwrap(); let level = chain.get_tip().unwrap(); - assert_eq!(level.height, i); + assert_eq!(level.height(), i); assert_eq!(level.block_in_main_chain().unwrap().original_header.nonce, i); } let level = chain.get_tip().unwrap(); - assert_eq!(level.height, 9); + 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_block.unwrap().hash); + 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 prev_block = Some((*chain.level_at_height(7).unwrap().block_in_main_chain().unwrap()).clone()); @@ -1763,14 +1769,14 @@ mod test { 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!(level.height(), 8); assert_eq!(chain.total_accumulated_tip_difficulty().as_u128(), 172); - assert_eq!(chain.level_at_height(9).unwrap().chain_block, FixedHash::default()); + assert_eq!(chain.level_at_height(9).unwrap().chain_block(), FixedHash::default()); } #[test] fn difficulty_go_up() { - let mut chain = P2Chain::new_empty(10, 5, 10); + let mut chain = P2Chain::new_empty(10, 5, 10, LmdbBlockStorage::new_from_temp_dir()); let mut prev_block = None; let mut tari_block = Block::new(BlockHeader::new(0), AggregateBody::empty()); @@ -1789,7 +1795,7 @@ mod test { assert!(target_difficulty > prev_target_difficulty); } let address = new_random_address(); - let block = P2BlockBuilder::new(prev_block.as_ref()) + let block = P2BlockBuilder::new(prev_block.as_deref()) .with_timestamp(timestamp) .with_height(i) .with_tari_block(tari_block.clone()) @@ -1803,13 +1809,13 @@ mod test { chain.add_block_to_chain(block.clone()).unwrap(); let level = chain.get_tip().unwrap(); - assert_eq!(level.height, i); + assert_eq!(level.height(), i); assert_eq!(level.block_in_main_chain().unwrap().original_header.nonce, i); } } #[test] fn difficulty_go_down() { - let mut chain = P2Chain::new_empty(10, 5, 10); + let mut chain = P2Chain::new_empty(10, 5, 10, LmdbBlockStorage::new_from_temp_dir()); let mut prev_block = None; let mut tari_block = Block::new(BlockHeader::new(0), AggregateBody::empty()); @@ -1828,7 +1834,7 @@ mod test { assert!(target_difficulty < prev_target_difficulty); } let address = new_random_address(); - let block = P2BlockBuilder::new(prev_block.as_ref()) + let block = P2BlockBuilder::new(prev_block.as_deref()) .with_timestamp(timestamp) .with_height(i) .with_tari_block(tari_block.clone()) @@ -1842,7 +1848,7 @@ mod test { chain.add_block_to_chain(block.clone()).unwrap(); let level = chain.get_tip().unwrap(); - assert_eq!(level.height, i); + assert_eq!(level.height(), i); assert_eq!(level.block_in_main_chain().unwrap().original_header.nonce, i); } } @@ -1852,7 +1858,7 @@ mod test { // This test adds a block to the tip, and then adds second block, // but has an uncle that is not in the chain. This test checks that // the tip is not set to the new block, because the uncle is missing. - let mut chain = P2Chain::new_empty(10, 5, 10); + let mut chain = P2Chain::new_empty(10, 5, 10, LmdbBlockStorage::new_from_temp_dir()); let prev_block = None; @@ -1885,7 +1891,7 @@ mod test { #[test] fn test_only_reorg_to_chain_if_it_is_verified() { - let mut chain = P2Chain::new_empty(10, 5, 10); + let mut chain = P2Chain::new_empty(10, 5, 10, LmdbBlockStorage::new_from_temp_dir()); let prev_block = None; let block = P2BlockBuilder::new(prev_block.as_ref()) @@ -1936,13 +1942,13 @@ mod test { .unwrap(); assert_eq!(chain.current_tip, 1); - assert_eq!(chain.get_tip().unwrap().chain_block, block2.hash); + assert_eq!(chain.get_tip().unwrap().chain_block(), block2.hash); 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); + assert_eq!(chain.get_tip().unwrap().chain_block(), block2.hash); chain.add_block_to_chain(unverified_uncle).unwrap(); @@ -1950,7 +1956,7 @@ mod test { 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); + assert_eq!(chain.get_tip().unwrap().chain_block(), block2b.hash); } fn diff(i: u64) -> Difficulty { diff --git a/src/sharechain/p2chain_level.rs b/src/sharechain/p2chain_level.rs index 3dc661e..7e31dc5 100644 --- a/src/sharechain/p2chain_level.rs +++ b/src/sharechain/p2chain_level.rs @@ -1,9 +1,3 @@ -use std::{collections::HashMap, sync::Arc}; - -use tari_common_types::types::{BlockHash, FixedHash}; - -use crate::sharechain::{error::ShareChainError, p2block::P2Block}; - // Copyright 2024. The Tari Project // // Redistribution and use in source and binary forms, with or without modification, are permitted provided that the @@ -18,57 +12,102 @@ use crate::sharechain::{error::ShareChainError, p2block::P2Block}; // 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote // products derived from this software without specific prior written permission. // -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, -// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +// DAMAGE. + +use std::sync::{Arc, RwLock}; + +use tari_common_types::types::{BlockHash, FixedHash}; + +use super::lmdb_block_storage::BlockCache; +use crate::sharechain::{error::ShareChainError, p2block::P2Block}; + /// A collection of blocks with the same height. -#[derive(Debug, Clone)] -pub struct P2ChainLevel { - pub blocks: HashMap>, - pub height: u64, - pub chain_block: BlockHash, +pub struct P2ChainLevel { + // pub blocks: HashMap>, + block_cache: Arc, + height: u64, + chain_block: RwLock, + block_hashes: RwLock>, } -impl P2ChainLevel { - pub fn new(block: Arc) -> Self { - let mut blocks = HashMap::new(); +impl P2ChainLevel { + pub fn new(block: Arc, block_cache: Arc) -> Self { // although this is the only block on this level, it might not be part of the main chain, so we need to set this // later - let chain_block = FixedHash::zero(); + let chain_block = RwLock::new(FixedHash::zero()); let height = block.height; - blocks.insert(block.hash, block); + let hash = block.hash; + block_cache.insert(block.hash, block); Self { - blocks, + block_cache, height, chain_block, + block_hashes: RwLock::new(vec![hash]), } } - pub fn add_block(&mut self, block: Arc) -> Result<(), ShareChainError> { + pub fn height(&self) -> u64 { + self.height + } + + pub fn chain_block(&self) -> BlockHash { + *self.chain_block.read().expect("read lock") + } + + pub fn set_chain_block(&self, hash: BlockHash) { + let mut lock = self.chain_block.write().expect("could not lock"); + *lock = hash; + } + + pub fn add_block(&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(), }); } - self.blocks.insert(block.hash, block); + self.block_hashes.write().expect("could not lock").push(block.hash); + self.block_cache.insert(block.hash, block); Ok(()) } - pub fn block_in_main_chain(&self) -> Option<&Arc> { - self.blocks.get(&self.chain_block) + pub fn block_in_main_chain(&self) -> Option> { + self.block_cache.get(&self.chain_block()) + } + + pub fn get(&self, hash: &BlockHash) -> Option> { + self.block_cache.get(hash) + } + + pub fn contains(&self, hash: &BlockHash) -> bool { + self.block_cache.contains(hash) + } + + pub fn all_blocks(&self) -> Vec> { + self.block_hashes + .read() + .expect("could not lock") + .iter() + .filter_map(|hash| self.block_cache.get(hash)) + .collect() } } #[cfg(test)] mod test { + use std::sync::Arc; + use tari_utilities::epoch_time::EpochTime; use crate::sharechain::{ in_memory::test::new_random_address, + lmdb_block_storage::test::InMemoryBlockCache, p2block::P2BlockBuilder, p2chain_level::P2ChainLevel, }; @@ -82,8 +121,8 @@ mod test { .with_miner_wallet_address(address.clone()) .build() .unwrap(); - let mut chain_level = P2ChainLevel::new(block.clone()); - chain_level.chain_block = block.generate_hash(); + let chain_level = P2ChainLevel::new(block.clone(), Arc::new(InMemoryBlockCache::new())); + chain_level.set_chain_block(block.generate_hash()); assert_eq!( chain_level.block_in_main_chain().unwrap().generate_hash(), @@ -112,7 +151,7 @@ mod test { .unwrap(); chain_level.add_block(block_3.clone()).unwrap(); - chain_level.chain_block = block_3.generate_hash(); + chain_level.set_chain_block(block_3.generate_hash()); assert_eq!( chain_level.block_in_main_chain().unwrap().generate_hash(),