// Copyright 2020. 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::{collections::HashMap, sync::Arc}; use log::*; use rand::{rngs::OsRng, RngCore}; use tari_common::configuration::Network; use tari_common_types::types::FixedHash; use tari_core::{ blocks::Block, chain_storage::{BlockAddResult, BlockchainDatabase, ChainStorageError}, consensus::ConsensusManager, proof_of_work::Difficulty, test_helpers::blockchain::TempDatabase, transactions::{key_manager::MemoryDbKeyManager, transaction_components::WalletOutput}, }; use crate::helpers::{ block_builders::{chain_block_with_new_coinbase, find_header_with_achieved_difficulty}, block_proxy::BlockProxy, sample_blockchains::create_new_blockchain, test_block_builder::{TestBlockBuilder, TestBlockBuilderInner}, }; const LOG_TARGET: &str = "tari_core::tests::helpers::test_blockchain"; pub struct TestBlockchain { store: BlockchainDatabase, blocks: HashMap, hash_to_block: HashMap, consensus_manager: ConsensusManager, outputs: Vec>, pub key_manager: MemoryDbKeyManager, } #[allow(dead_code)] impl TestBlockchain { pub async fn with_genesis(genesis_name: &'static str) -> Self { let network = Network::LocalNet; let (store, mut b, outputs, consensus_manager, key_manager) = create_new_blockchain(network).await; let name = genesis_name.to_string(); let mut blocks = HashMap::new(); let genesis_block = b.pop().unwrap(); let mut hash_to_block = HashMap::new(); hash_to_block.insert(*genesis_block.hash(), name.clone()); blocks.insert(name.clone(), BlockProxy::new(name, genesis_block)); Self { store, blocks, hash_to_block, consensus_manager, outputs, key_manager, } } pub fn store(&self) -> &BlockchainDatabase { &self.store } pub fn consensus_manager(&self) -> &ConsensusManager { &self.consensus_manager } pub async fn build_block(&self, block: TestBlockBuilderInner) -> (Block, WalletOutput) { debug!(target: LOG_TARGET, "Adding block '{}' to test block chain", block.name); let prev_block = self.blocks.get(&block.child_of.unwrap()); let prev_block = prev_block.map(|b| &b.block).unwrap(); let (template, output) = chain_block_with_new_coinbase( prev_block, block.transactions, &self.consensus_manager, None, &self.key_manager, ) .await; let mut new_block = self.store.prepare_new_block(template).unwrap(); new_block.header.nonce = OsRng.next_u64(); find_header_with_achieved_difficulty( &mut new_block.header, Difficulty::from_u64(block.difficulty.unwrap_or(1)).unwrap(), ); (new_block, output) } pub async fn add_block(&mut self, block: TestBlockBuilderInner) -> (BlockAddResult, WalletOutput) { let block_name = block.name.clone(); let (block, output) = self.build_block(block).await; self.outputs.push(vec![output.clone()]); let res = self.add_raw_block(&block_name, block).unwrap(); (res, output) } pub fn add_raw_block(&mut self, block_name: &str, block: Block) -> Result { let res = self.store.add_block(Arc::new(block))?; if let BlockAddResult::Ok(ref b) = res { self.hash_to_block.insert(*b.hash(), block_name.to_string()); self.blocks.insert( block_name.to_string(), BlockProxy::new(block_name.to_string(), b.as_ref().clone()), ); } Ok(res) } pub fn outputs_at(&self, height: u64) -> &[WalletOutput] { #[allow(clippy::cast_possible_truncation)] self.outputs.get(height as usize).unwrap() } pub fn builder(&mut self) -> TestBlockBuilder { TestBlockBuilder {} } pub fn orphan_count(&self) -> usize { self.store.orphan_count().unwrap() } pub fn tip(&self) -> &BlockProxy { let tip = self.store.fetch_tip_header().unwrap(); self.get_block_by_hash(tip.hash()).unwrap() } pub fn get_block(&self, name: &str) -> Option<&BlockProxy> { self.blocks.get(name) } pub fn get_block_by_hash(&self, hash: &FixedHash) -> Option<&BlockProxy> { let block_name = self.hash_to_block.get(hash); block_name.map(|bn| self.blocks.get(bn).unwrap()) } pub fn chain(&self) -> Vec<&str> { let mut result = vec![]; let (mut tip, _) = self.store.fetch_tip_header().unwrap().into_parts(); while tip.height > 0 { result.push(self.get_block_by_hash(&tip.hash()).unwrap().name.as_str()); tip = self.store.fetch_header_by_block_hash(tip.prev_hash).unwrap().unwrap(); } result.push(self.get_block_by_hash(&tip.hash()).unwrap().name.as_str()); result.reverse(); result } }