diff --git a/applications/tari_app_grpc/src/conversions/transaction_input.rs b/applications/tari_app_grpc/src/conversions/transaction_input.rs index 025e0b900d..0a42d1d61d 100644 --- a/applications/tari_app_grpc/src/conversions/transaction_input.rs +++ b/applications/tari_app_grpc/src/conversions/transaction_input.rs @@ -114,10 +114,7 @@ impl TryFrom for grpc::TransactionInput { .commitment() .map_err(|_| "Non-compact Transaction input should contain commitment".to_string())? .to_vec(), - hash: input - .canonical_hash() - .map_err(|_| "Non-compact Transaction input should be able to be hashed".to_string())? - .to_vec(), + hash: input.canonical_hash().to_vec(), script: input .script() diff --git a/applications/tari_base_node/src/commands/command/check_db.rs b/applications/tari_base_node/src/commands/command/check_db.rs index bc9bf2aa2b..ff37023a01 100644 --- a/applications/tari_base_node/src/commands/command/check_db.rs +++ b/applications/tari_base_node/src/commands/command/check_db.rs @@ -54,7 +54,7 @@ impl CommandContext { io::stdout().flush().await?; // we can only check till the pruning horizon, 0 is archive node so it needs to check every block. if height > horizon_height { - match self.node_service.get_block(height).await { + match self.node_service.get_block(height, false).await { Err(err) => { // We need to check the data itself, as FetchMatchingBlocks will suppress any error, only // logging it. diff --git a/applications/tari_base_node/src/commands/command/get_block.rs b/applications/tari_base_node/src/commands/command/get_block.rs index d2c9526753..9042cbf427 100644 --- a/applications/tari_base_node/src/commands/command/get_block.rs +++ b/applications/tari_base_node/src/commands/command/get_block.rs @@ -70,7 +70,7 @@ impl CommandContext { pub async fn get_block(&self, height: u64, format: Format) -> Result<(), Error> { let block = self .blockchain_db - .fetch_blocks(height..=height) + .fetch_blocks(height..=height, false) .await? .pop() .ok_or(ArgsError::NotFoundAt { height })?; @@ -90,7 +90,7 @@ impl CommandContext { pub async fn get_block_by_hash(&self, hash: HashOutput, format: Format) -> Result<(), Error> { let block = self .blockchain_db - .fetch_block_by_hash(hash) + .fetch_block_by_hash(hash, false) .await? .ok_or(ArgsError::NotFound)?; match format { diff --git a/applications/tari_base_node/src/commands/command/period_stats.rs b/applications/tari_base_node/src/commands/command/period_stats.rs index f240eabab3..845bbc5349 100644 --- a/applications/tari_base_node/src/commands/command/period_stats.rs +++ b/applications/tari_base_node/src/commands/command/period_stats.rs @@ -75,13 +75,13 @@ impl CommandContext { let block = self .node_service - .get_block(height) + .get_block(height, true) .await? .ok_or_else(|| anyhow!("Error in db, block not found at height {}", height))?; let prev_block = self .node_service - .get_block(height - 1) + .get_block(height - 1, true) .await? .ok_or_else(|| anyhow!("Error in db, block not found at height {}", height))?; diff --git a/applications/tari_base_node/src/grpc/base_node_grpc_server.rs b/applications/tari_base_node/src/grpc/base_node_grpc_server.rs index ba7afd2200..68683f67b7 100644 --- a/applications/tari_base_node/src/grpc/base_node_grpc_server.rs +++ b/applications/tari_base_node/src/grpc/base_node_grpc_server.rs @@ -384,7 +384,7 @@ impl tari_rpc::base_node_server::BaseNode for BaseNodeGrpcServer { for (start, end) in page_iter { debug!(target: LOG_TARGET, "Page: {}-{}", start, end); // TODO: Better error handling - let result_data = match handler.get_blocks(start..=end).await { + let result_data = match handler.get_blocks(start..=end, true).await { Err(err) => { warn!(target: LOG_TARGET, "Internal base node service error: {}", err); return; @@ -849,7 +849,7 @@ impl tari_rpc::base_node_server::BaseNode for BaseNodeGrpcServer { task::spawn(async move { let page_iter = NonOverlappingIntegerPairIter::new(start, end + 1, GET_BLOCKS_PAGE_SIZE); for (start, end) in page_iter { - let blocks = match handler.get_blocks(start..=end).await { + let blocks = match handler.get_blocks(start..=end, false).await { Err(err) => { warn!( target: LOG_TARGET, @@ -1454,7 +1454,7 @@ async fn get_block_group( let (start, end) = get_heights(&height_request, handler.clone()).await?; - let blocks = match handler.get_blocks(start..=end).await { + let blocks = match handler.get_blocks(start..=end, false).await { Err(err) => { warn!( target: LOG_TARGET, diff --git a/applications/tari_base_node/src/recovery.rs b/applications/tari_base_node/src/recovery.rs index eb9eea3b4b..3ab23c7705 100644 --- a/applications/tari_base_node/src/recovery.rs +++ b/applications/tari_base_node/src/recovery.rs @@ -153,7 +153,7 @@ async fn do_recovery( io::stdout().flush().unwrap(); trace!(target: LOG_TARGET, "Asking for block with height: {}", counter); let block = source_database - .fetch_block(counter) + .fetch_block(counter, true) .map_err(|e| anyhow!("Could not get block from recovery db: {}", e))? .try_into_block()?; trace!(target: LOG_TARGET, "Adding block: {}", block); diff --git a/base_layer/core/src/base_node/comms_interface/comms_request.rs b/base_layer/core/src/base_node/comms_interface/comms_request.rs index 60d35ff753..19d6c574ae 100644 --- a/base_layer/core/src/base_node/comms_interface/comms_request.rs +++ b/base_layer/core/src/base_node/comms_interface/comms_request.rs @@ -46,8 +46,14 @@ pub enum NodeCommsRequest { FetchHeaders(RangeInclusive), FetchHeadersByHashes(Vec), FetchMatchingUtxos(Vec), - FetchMatchingBlocks(RangeInclusive), - FetchBlocksByHash(Vec), + FetchMatchingBlocks { + range: RangeInclusive, + compact: bool, + }, + FetchBlocksByHash { + block_hashes: Vec, + compact: bool, + }, FetchBlocksByKernelExcessSigs(Vec), FetchBlocksByUtxos(Vec), GetHeaderByHash(HashOutput), @@ -55,7 +61,9 @@ pub enum NodeCommsRequest { GetNewBlockTemplate(GetNewBlockTemplateRequest), GetNewBlock(NewBlockTemplate), FetchKernelByExcessSig(Signature), - FetchMempoolTransactionsByExcessSigs { excess_sigs: Vec }, + FetchMempoolTransactionsByExcessSigs { + excess_sigs: Vec, + }, } #[derive(Debug, Serialize, Deserialize)] @@ -75,10 +83,12 @@ impl Display for NodeCommsRequest { }, FetchHeadersByHashes(v) => write!(f, "FetchHeadersByHashes (n={})", v.len()), FetchMatchingUtxos(v) => write!(f, "FetchMatchingUtxos (n={})", v.len()), - FetchMatchingBlocks(range) => { - write!(f, "FetchMatchingBlocks ({:?})", range) + FetchMatchingBlocks { range, compact } => { + write!(f, "FetchMatchingBlocks ({:?}, {})", range, compact) + }, + FetchBlocksByHash { block_hashes, compact } => { + write!(f, "FetchBlocksByHash (n={}, {})", block_hashes.len(), compact) }, - FetchBlocksByHash(v) => write!(f, "FetchBlocksByHash (n={})", v.len()), FetchBlocksByKernelExcessSigs(v) => write!(f, "FetchBlocksByKernelExcessSigs (n={})", v.len()), FetchBlocksByUtxos(v) => write!(f, "FetchBlocksByUtxos (n={})", v.len()), GetHeaderByHash(v) => write!(f, "GetHeaderByHash({})", v.to_hex()), diff --git a/base_layer/core/src/base_node/comms_interface/error.rs b/base_layer/core/src/base_node/comms_interface/error.rs index 5ff845d60f..cc1f1b2314 100644 --- a/base_layer/core/src/base_node/comms_interface/error.rs +++ b/base_layer/core/src/base_node/comms_interface/error.rs @@ -20,6 +20,7 @@ // 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 tari_common_types::types::FixedHash; use tari_comms_dht::outbound::DhtOutboundError; use tari_service_framework::reply_channel::TransportChannelError; use thiserror::Error; @@ -67,4 +68,6 @@ pub enum CommsInterfaceError { BlockError(#[from] BlockError), #[error("Invalid request for {request}: {details}")] InvalidRequest { request: &'static str, details: String }, + #[error("Peer sent invalid full block {hash}: {details}")] + InvalidFullBlock { hash: FixedHash, details: String }, } diff --git a/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs b/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs index 1067179be8..26151d7026 100644 --- a/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs +++ b/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs @@ -50,6 +50,7 @@ use crate::{ consensus::{ConsensusConstants, ConsensusManager}, mempool::Mempool, proof_of_work::{Difficulty, PowAlgorithm}, + transactions::aggregated_body::AggregateBody, validation::helpers, }; @@ -164,20 +165,20 @@ where B: BlockchainBackend + 'static } Ok(NodeCommsResponse::TransactionOutputs(res)) }, - NodeCommsRequest::FetchMatchingBlocks(range) => { - let blocks = self.blockchain_db.fetch_blocks(range).await?; + NodeCommsRequest::FetchMatchingBlocks { range, compact } => { + let blocks = self.blockchain_db.fetch_blocks(range, compact).await?; Ok(NodeCommsResponse::HistoricalBlocks(blocks)) }, - NodeCommsRequest::FetchBlocksByHash(block_hashes) => { + NodeCommsRequest::FetchBlocksByHash { block_hashes, compact } => { let mut blocks = Vec::with_capacity(block_hashes.len()); for block_hash in block_hashes { let block_hex = block_hash.to_hex(); debug!( target: LOG_TARGET, - "A peer has requested a block with hash {}", block_hex + "A peer has requested a block with hash {} (compact = {})", block_hex, compact ); - match self.blockchain_db.fetch_block_by_hash(block_hash).await { + match self.blockchain_db.fetch_block_by_hash(block_hash, compact).await { Ok(Some(block)) => blocks.push(block), Ok(None) => warn!( target: LOG_TARGET, @@ -269,7 +270,7 @@ where B: BlockchainBackend + 'static Ok(NodeCommsResponse::BlockHeader(header)) }, NodeCommsRequest::GetBlockByHash(hash) => { - let block = self.blockchain_db.fetch_block_by_hash(hash).await?; + let block = self.blockchain_db.fetch_block_by_hash(hash, false).await?; Ok(NodeCommsResponse::HistoricalBlock(Box::new(block))) }, NodeCommsRequest::GetNewBlockTemplate(request) => { @@ -422,7 +423,7 @@ where B: BlockchainBackend + 'static &mut self, source_peer: NodeId, new_block: NewBlock, - ) -> Result, CommsInterfaceError> { + ) -> Result { let NewBlock { header, coinbase_kernel, @@ -436,7 +437,7 @@ where B: BlockchainBackend + 'static .with_coinbase_utxo(coinbase_output, coinbase_kernel) .with_header(header) .build(); - return Ok(Arc::new(block)); + return Ok(block); } let block_hash = header.hash(); @@ -454,6 +455,7 @@ where B: BlockchainBackend + 'static current_meta.best_block().to_hex(), source_peer, ); + metrics::compact_block_tx_misses(header.height).set(excess_sigs.len() as i64); let block = self.request_full_block_from_peer(source_peer, block_hash).await?; return Ok(block); } @@ -549,14 +551,14 @@ where B: BlockchainBackend + 'static return Ok(block); } - Ok(Arc::new(block)) + Ok(block) } async fn request_full_block_from_peer( &mut self, source_peer: NodeId, block_hash: BlockHash, - ) -> Result, CommsInterfaceError> { + ) -> Result { let mut historical_block = self .outbound_nci .request_blocks_by_hashes_from_peer(vec![block_hash], Some(source_peer.clone())) @@ -564,7 +566,7 @@ where B: BlockchainBackend + 'static return match historical_block.pop() { Some(block) => { - let block = Arc::new(block.try_into_block()?); + let block = block.try_into_block()?; Ok(block) }, None => { @@ -600,7 +602,7 @@ where B: BlockchainBackend + 'static /// source_peer - the peer that sent this new block message, or None if the block was generated by a local miner pub async fn handle_block( &mut self, - block: Arc, + block: Block, source_peer: Option, ) -> Result { let block_hash = block.hash(); @@ -618,6 +620,8 @@ where B: BlockchainBackend + 'static ); debug!(target: LOG_TARGET, "Incoming block: {}", block); let timer = Instant::now(); + let block = self.hydrate_block(block).await?; + let add_block_result = self.blockchain_db.add_block(block.clone()).await; // Create block event on block event stream match add_block_result { @@ -691,6 +695,68 @@ where B: BlockchainBackend + 'static } } + async fn hydrate_block(&mut self, block: Block) -> Result, CommsInterfaceError> { + let block_hash = block.hash(); + let block_height = block.header.height; + if block.body.inputs().is_empty() { + debug!( + target: LOG_TARGET, + "Block #{} ({}) contains no inputs so nothing to hydrate", + block_height, + block_hash.to_hex(), + ); + return Ok(Arc::new(block)); + } + + let timer = Instant::now(); + let (header, mut inputs, outputs, kernels) = block.dissolve(); + + let db = self.blockchain_db.inner().db_read_access()?; + for input in &mut inputs { + if !input.is_compact() { + continue; + } + + let output_mined_info = + db.fetch_output(&input.output_hash())? + .ok_or_else(|| CommsInterfaceError::InvalidFullBlock { + hash: block_hash, + details: format!("Output {} to be spent does not exist in db", input.output_hash()), + })?; + + match output_mined_info.output { + PrunedOutput::Pruned { .. } => { + return Err(CommsInterfaceError::InvalidFullBlock { + hash: block_hash, + details: format!("Output {} to be spent is pruned", input.output_hash()), + }); + }, + PrunedOutput::NotPruned { output } => { + input.add_output_data( + output.version, + output.features, + output.commitment, + output.script, + output.sender_offset_public_key, + output.covenant, + output.encrypted_value, + output.minimum_value_promise, + ); + }, + } + } + debug!( + target: LOG_TARGET, + "Hydrated block #{} ({}) with {} input(s) in {:.2?}", + block_height, + block_hash.to_hex(), + inputs.len(), + timer.elapsed() + ); + let block = Block::new(header, AggregateBody::new(inputs, outputs, kernels)); + Ok(Arc::new(block)) + } + fn publish_block_event(&self, event: BlockEvent) { if let Err(event) = self.block_event_sender.send(Arc::new(event)) { debug!(target: LOG_TARGET, "No event subscribers. Event {} dropped.", event.0) diff --git a/base_layer/core/src/base_node/comms_interface/local_interface.rs b/base_layer/core/src/base_node/comms_interface/local_interface.rs index ee3789a663..54feaf45e7 100644 --- a/base_layer/core/src/base_node/comms_interface/local_interface.rs +++ b/base_layer/core/src/base_node/comms_interface/local_interface.rs @@ -84,10 +84,11 @@ impl LocalNodeCommsInterface { pub async fn get_blocks( &mut self, range: RangeInclusive, + compact: bool, ) -> Result, CommsInterfaceError> { match self .request_sender - .call(NodeCommsRequest::FetchMatchingBlocks(range)) + .call(NodeCommsRequest::FetchMatchingBlocks { range, compact }) .await?? { NodeCommsResponse::HistoricalBlocks(blocks) => Ok(blocks), @@ -96,10 +97,17 @@ impl LocalNodeCommsInterface { } /// Request the block header at the given height - pub async fn get_block(&mut self, height: u64) -> Result, CommsInterfaceError> { + pub async fn get_block( + &mut self, + height: u64, + compact: bool, + ) -> Result, CommsInterfaceError> { match self .request_sender - .call(NodeCommsRequest::FetchMatchingBlocks(height..=height)) + .call(NodeCommsRequest::FetchMatchingBlocks { + range: height..=height, + compact, + }) .await?? { NodeCommsResponse::HistoricalBlocks(mut blocks) => Ok(blocks.pop()), diff --git a/base_layer/core/src/base_node/comms_interface/outbound_interface.rs b/base_layer/core/src/base_node/comms_interface/outbound_interface.rs index 5fe89c9a11..662a58fa89 100644 --- a/base_layer/core/src/base_node/comms_interface/outbound_interface.rs +++ b/base_layer/core/src/base_node/comms_interface/outbound_interface.rs @@ -57,8 +57,7 @@ impl OutboundNodeCommsInterface { } } - /// Fetch the Blocks corresponding to the provided block hashes from a specific base node. The requested blocks - /// could be chain blocks or orphan blocks. + /// Fetch the Blocks corresponding to the provided block hashes from a specific base node. pub async fn request_blocks_by_hashes_from_peer( &mut self, block_hashes: Vec, @@ -66,7 +65,14 @@ impl OutboundNodeCommsInterface { ) -> Result, CommsInterfaceError> { if let NodeCommsResponse::HistoricalBlocks(blocks) = self .request_sender - .call((NodeCommsRequest::FetchBlocksByHash(block_hashes), node_id)) + .call(( + NodeCommsRequest::FetchBlocksByHash { + block_hashes, + // We always request compact inputs from peer + compact: true, + }, + node_id, + )) .await?? { Ok(blocks) diff --git a/base_layer/core/src/base_node/proto/request.proto b/base_layer/core/src/base_node/proto/request.proto index 34d3534f06..1c60133514 100644 --- a/base_layer/core/src/base_node/proto/request.proto +++ b/base_layer/core/src/base_node/proto/request.proto @@ -13,7 +13,7 @@ message BaseNodeServiceRequest { uint64 request_key = 1; oneof request { // Indicates a FetchBlocksByHash request. - HashOutputs fetch_blocks_by_hash = 8; + FetchBlocksByHashRequest fetch_blocks_by_hash = 8; ExcessSigs fetch_mempool_transactions_by_excess_sigs = 9; } } @@ -27,8 +27,9 @@ message BlockHeights { repeated uint64 heights = 1; } -message HashOutputs { - repeated bytes outputs = 1; +message FetchBlocksByHashRequest { + repeated bytes block_hashes = 1; + bool compact = 2; } message Signatures { diff --git a/base_layer/core/src/base_node/proto/request.rs b/base_layer/core/src/base_node/proto/request.rs index fc61325274..d5c39fcdcd 100644 --- a/base_layer/core/src/base_node/proto/request.rs +++ b/base_layer/core/src/base_node/proto/request.rs @@ -22,7 +22,7 @@ use std::convert::{From, TryFrom, TryInto}; -use tari_common_types::types::{HashOutput, PrivateKey}; +use tari_common_types::types::PrivateKey; use tari_utilities::ByteArray; use crate::{ @@ -37,13 +37,16 @@ impl TryInto for ProtoNodeCommsRequest { fn try_into(self) -> Result { use ProtoNodeCommsRequest::{FetchBlocksByHash, FetchMempoolTransactionsByExcessSigs}; let request = match self { - FetchBlocksByHash(block_hashes) => { - let hashes = block_hashes - .outputs + FetchBlocksByHash(req) => { + let block_hashes = req + .block_hashes .into_iter() .map(|hash| hash.try_into().map_err(|_| "Malformed hash".to_string())) .collect::>()?; - NodeCommsRequest::FetchBlocksByHash(hashes) + NodeCommsRequest::FetchBlocksByHash { + block_hashes, + compact: req.compact, + } }, FetchMempoolTransactionsByExcessSigs(excess_sigs) => { let excess_sigs = excess_sigs @@ -65,7 +68,12 @@ impl TryFrom for ProtoNodeCommsRequest { fn try_from(request: NodeCommsRequest) -> Result { use NodeCommsRequest::{FetchBlocksByHash, FetchMempoolTransactionsByExcessSigs}; match request { - FetchBlocksByHash(block_hashes) => Ok(ProtoNodeCommsRequest::FetchBlocksByHash(block_hashes.into())), + FetchBlocksByHash { block_hashes, compact } => Ok(ProtoNodeCommsRequest::FetchBlocksByHash( + proto::FetchBlocksByHashRequest { + block_hashes: block_hashes.into_iter().map(|hash| hash.to_vec()).collect(), + compact, + }, + )), FetchMempoolTransactionsByExcessSigs { excess_sigs } => Ok( ProtoNodeCommsRequest::FetchMempoolTransactionsByExcessSigs(proto::ExcessSigs { excess_sigs: excess_sigs.into_iter().map(|sig| sig.to_vec()).collect(), @@ -78,13 +86,6 @@ impl TryFrom for ProtoNodeCommsRequest { //---------------------------------- Wrappers --------------------------------------------// -impl From> for proto::HashOutputs { - fn from(outputs: Vec) -> Self { - let hashes = outputs.iter().map(|v| v.to_vec()).collect(); - Self { outputs: hashes } - } -} - impl From> for proto::BlockHeights { fn from(heights: Vec) -> Self { Self { heights } diff --git a/base_layer/core/src/base_node/service/service.rs b/base_layer/core/src/base_node/service/service.rs index 9b97ce13d4..7be222d098 100644 --- a/base_layer/core/src/base_node/service/service.rs +++ b/base_layer/core/src/base_node/service/service.rs @@ -20,7 +20,7 @@ // 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::{convert::TryInto, sync::Arc, time::Duration}; +use std::{convert::TryInto, time::Duration}; use futures::{pin_mut, stream::StreamExt, Stream}; use log::*; @@ -338,7 +338,7 @@ where B: BlockchainBackend + 'static let mut inbound_nch = self.inbound_nch.clone(); task::spawn(async move { let (block, reply_tx) = block_context.split(); - let result = reply_tx.send(inbound_nch.handle_block(Arc::new(block), None).await); + let result = reply_tx.send(inbound_nch.handle_block(block, None).await); if let Err(res) = result { error!( diff --git a/base_layer/core/src/base_node/sync/rpc/service.rs b/base_layer/core/src/base_node/sync/rpc/service.rs index 8ea2c04ce1..f1334fdd08 100644 --- a/base_layer/core/src/base_node/sync/rpc/service.rs +++ b/base_layer/core/src/base_node/sync/rpc/service.rs @@ -206,7 +206,7 @@ impl BaseNodeSyncService for BaseNodeSyncRpcServ "Sending blocks #{} - #{} to '{}'", start, end, peer_node_id ); let blocks = db - .fetch_blocks(start..=end) + .fetch_blocks(start..=end, true) .await .map_err(RpcStatus::log_internal_error(LOG_TARGET)); @@ -225,12 +225,7 @@ impl BaseNodeSyncService for BaseNodeSyncRpcServ Ok(blocks) => { let blocks = blocks .into_iter() - .map(|hb| { - match hb.try_into_block().map_err(RpcStatus::log_internal_error(LOG_TARGET)) { - Ok(b) => Ok(b.to_compact()), - Err(e) => Err(e), - } - }) + .map(|hb| hb.try_into_block().map_err(RpcStatus::log_internal_error(LOG_TARGET))) .map(|block| match block { Ok(b) => proto::base_node::BlockBodyResponse::try_from(b).map_err(|e| { log::error!(target: LOG_TARGET, "Internal error: {}", e); diff --git a/base_layer/core/src/chain_storage/async_db.rs b/base_layer/core/src/chain_storage/async_db.rs index 031e6405b1..133f445bc4 100644 --- a/base_layer/core/src/chain_storage/async_db.rs +++ b/base_layer/core/src/chain_storage/async_db.rs @@ -223,13 +223,13 @@ impl AsyncBlockchainDb { make_async_fn!(bad_block_exists(block_hash: BlockHash) -> bool, "bad_block_exists"); - make_async_fn!(fetch_block(height: u64) -> HistoricalBlock, "fetch_block"); + make_async_fn!(fetch_block(height: u64, compact: bool) -> HistoricalBlock, "fetch_block"); - make_async_fn!(fetch_blocks>(bounds: T) -> Vec, "fetch_blocks"); + make_async_fn!(fetch_blocks>(bounds: T, compact: bool) -> Vec, "fetch_blocks"); make_async_fn!(fetch_orphan(hash: HashOutput) -> Block, "fetch_orphan"); - make_async_fn!(fetch_block_by_hash(hash: HashOutput) -> Option, "fetch_block_by_hash"); + make_async_fn!(fetch_block_by_hash(hash: HashOutput, compact: bool) -> Option, "fetch_block_by_hash"); make_async_fn!(fetch_block_with_kernel(excess_sig: Signature) -> Option, "fetch_block_with_kernel"); diff --git a/base_layer/core/src/chain_storage/blockchain_database.rs b/base_layer/core/src/chain_storage/blockchain_database.rs index 1caaf418a3..2b7d4c5da8 100644 --- a/base_layer/core/src/chain_storage/blockchain_database.rs +++ b/base_layer/core/src/chain_storage/blockchain_database.rs @@ -379,8 +379,7 @@ where B: BlockchainBackend } /// Return a list of matching utxos, with each being `None` if not found. If found, the transaction - /// output, and a boolean indicating if the UTXO was spent as of the block hash specified or the tip if not - /// specified. + /// output, and a boolean indicating if the UTXO was spent as of the current tip. pub fn fetch_utxos(&self, hashes: Vec) -> Result>, ChainStorageError> { let db = self.db_read_access()?; let deleted = db.fetch_deleted_bitmap()?; @@ -995,13 +994,17 @@ where B: BlockchainBackend /// * There is an access problem on the back end. /// * The height is beyond the current chain tip. /// * The height is lower than the block at the pruning horizon. - pub fn fetch_block(&self, height: u64) -> Result { + pub fn fetch_block(&self, height: u64, compact: bool) -> Result { let db = self.db_read_access()?; - fetch_block(&*db, height) + fetch_block(&*db, height, compact) } /// Returns the set of blocks according to the bounds - pub fn fetch_blocks>(&self, bounds: T) -> Result, ChainStorageError> { + pub fn fetch_blocks>( + &self, + bounds: T, + compact: bool, + ) -> Result, ChainStorageError> { let db = self.db_read_access()?; let (mut start, mut end) = convert_to_option_bounds(bounds); @@ -1027,31 +1030,34 @@ where B: BlockchainBackend } debug!(target: LOG_TARGET, "Fetching blocks {}-{}", start, end); - let blocks = fetch_blocks(&*db, start, end)?; + let blocks = fetch_blocks(&*db, start, end, compact)?; debug!(target: LOG_TARGET, "Fetched {} block(s)", blocks.len()); Ok(blocks) } - /// Attempt to fetch the block corresponding to the provided hash from the main chain, if it cannot be found then - /// the block will be searched in the orphan block pool. - pub fn fetch_block_by_hash(&self, hash: BlockHash) -> Result, ChainStorageError> { + /// Attempt to fetch the block corresponding to the provided hash from the main chain + pub fn fetch_block_by_hash( + &self, + hash: BlockHash, + compact: bool, + ) -> Result, ChainStorageError> { let db = self.db_read_access()?; - fetch_block_by_hash(&*db, hash) + fetch_block_by_hash(&*db, hash, compact) } /// Attempt to fetch the block corresponding to the provided kernel hash from the main chain, if the block is past /// pruning horizon, it will return Ok pub fn fetch_block_with_kernel(&self, excess_sig: Signature) -> Result, ChainStorageError> { let db = self.db_read_access()?; - fetch_block_with_kernel(&*db, excess_sig) + fetch_block_by_kernel_signature(&*db, excess_sig) } /// Attempt to fetch the block corresponding to the provided utxo hash from the main chain, if the block is past /// pruning horizon, it will return Ok pub fn fetch_block_with_utxo(&self, commitment: Commitment) -> Result, ChainStorageError> { let db = self.db_read_access()?; - fetch_block_with_utxo(&*db, &commitment) + fetch_block_by_utxo_commitment(&*db, &commitment) } /// Returns true if this block exists in the chain, or is orphaned. @@ -1254,7 +1260,7 @@ pub fn calculate_mmr_roots(db: &T, block: &Block) -> Resul } for input in body.inputs().iter() { - input_mmr.push(input.canonical_hash()?.to_vec())?; + input_mmr.push(input.canonical_hash().to_vec())?; // Search the DB for the output leaf index so that it can be marked as spent/deleted. // If the output hash is not found, check the current output_mmr. This allows zero-conf transactions @@ -1476,7 +1482,7 @@ pub fn fetch_target_difficulty_for_next_block( Ok(target_difficulties) } -fn fetch_block(db: &T, height: u64) -> Result { +fn fetch_block(db: &T, height: u64, compact: bool) -> Result { let mark = Instant::now(); let (tip_height, is_pruned) = check_for_valid_height(&*db, height)?; let chain_header = db.fetch_chain_header_by_height(height)?; @@ -1488,6 +1494,9 @@ fn fetch_block(db: &T, height: u64) -> Result o, Ok(None) => { @@ -1576,17 +1585,18 @@ fn fetch_blocks( db: &T, start: u64, end_inclusive: u64, + compact: bool, ) -> Result, ChainStorageError> { - (start..=end_inclusive).map(|i| fetch_block(db, i)).collect() + (start..=end_inclusive).map(|i| fetch_block(db, i, compact)).collect() } -fn fetch_block_with_kernel( +fn fetch_block_by_kernel_signature( db: &T, excess_sig: Signature, ) -> Result, ChainStorageError> { match db.fetch_kernel_by_excess_sig(&excess_sig) { Ok(kernel) => match kernel { - Some((_kernel, hash)) => fetch_block_by_hash(db, hash), + Some((_kernel, hash)) => fetch_block_by_hash(db, hash, false), None => Ok(None), }, Err(_) => Err(ChainStorageError::ValueNotFound { @@ -1597,14 +1607,14 @@ fn fetch_block_with_kernel( } } -fn fetch_block_with_utxo( +fn fetch_block_by_utxo_commitment( db: &T, commitment: &Commitment, ) -> Result, ChainStorageError> { let output = db.fetch_unspent_output_hash_by_commitment(commitment)?; match output { Some(hash) => match db.fetch_output(&hash)? { - Some(mined_info) => fetch_block_by_hash(db, mined_info.header_hash), + Some(mined_info) => fetch_block_by_hash(db, mined_info.header_hash, false), None => Ok(None), }, None => Ok(None), @@ -1614,9 +1624,10 @@ fn fetch_block_with_utxo( fn fetch_block_by_hash( db: &T, hash: BlockHash, + compact: bool, ) -> Result, ChainStorageError> { if let Some(header) = fetch_header_by_block_hash(db, hash)? { - return Ok(Some(fetch_block(db, header.height)?)); + return Ok(Some(fetch_block(db, header.height, compact)?)); } Ok(None) } @@ -1709,7 +1720,7 @@ fn rewind_to_height( for h in 0..steps_back { info!(target: LOG_TARGET, "Deleting block {}", last_block_height - h,); - let block = fetch_block(db, last_block_height - h)?; + let block = fetch_block(db, last_block_height - h, false)?; let block = Arc::new(block.try_into_chain_block()?); txn.delete_block(*block.hash()); txn.delete_header(last_block_height - h); @@ -1733,8 +1744,8 @@ fn rewind_to_height( "Deleting blocks and utxos {}", last_block_height - h - steps_back, ); - let block = fetch_block(db, last_block_height - h - steps_back)?; - txn.delete_block(block.block().hash()); + let header = fetch_header(db, last_block_height - h - steps_back)?; + txn.delete_block(header.hash()); } } @@ -2000,6 +2011,31 @@ fn reorganize_chain( Ok(removed_blocks) } +// fn hydrate_block( +// backend: &mut T, +// block: Arc, +// ) -> Result, ChainStorageError> { +// if !block.block().body.has_compact_inputs() { +// return Ok(block); +// } +// +// for input in block.block().body.inputs() { +// let output = backend.fetch_mmr_leaf(MmrTree::Utxo, input.mmr_index())?; +// let output = output.ok_or_else(|| ChainStorageError::ValueNotFound { +// entity: "Output".to_string(), +// field: "mmr_index".to_string(), +// value: input.mmr_index().to_string(), +// })?; +// let output = TransactionOutput::try_from(output)?; +// let input = TransactionInput::new_with_commitment(input.features(), output.commitment()); +// block.block_mut().body_mut().add_input(input); +// } +// backend.fetch_unspent_output_hash_by_commitment() +// let block = hydrate_block_from_db(backend, block_hash, block.header().clone())?; +// txn.delete_orphan(block_hash); +// backend.write(txn)?; +// Ok(block) +// } fn restore_reorged_chain( db: &mut T, @@ -2454,7 +2490,12 @@ mod test { #[test] fn it_gets_a_simple_link_to_genesis() { let db = create_new_blockchain(); - let genesis = db.fetch_block(0).unwrap().try_into_chain_block().map(Arc::new).unwrap(); + let genesis = db + .fetch_block(0, true) + .unwrap() + .try_into_chain_block() + .map(Arc::new) + .unwrap(); let (_, chain) = create_orphan_chain(&db, &[("A->GB", 1, 120), ("B->A", 1, 120), ("C->B", 1, 120)], genesis); let access = db.db_read_access().unwrap(); @@ -2513,7 +2554,12 @@ mod test { fn it_inserts_new_block_in_orphan_db_as_tip() { let db = create_new_blockchain(); let validator = MockValidator::new(true); - let genesis_block = db.fetch_block(0).unwrap().try_into_chain_block().map(Arc::new).unwrap(); + let genesis_block = db + .fetch_block(0, true) + .unwrap() + .try_into_chain_block() + .map(Arc::new) + .unwrap(); let (_, chain) = create_chained_blocks(&[("A->GB", 1u64, 120u64)], genesis_block); let block = chain.get("A").unwrap().clone(); let mut access = db.db_write_access().unwrap(); @@ -3169,7 +3215,7 @@ mod test { // let db = create_new_blockchain(); let genesis_block = test .db - .fetch_block(0) + .fetch_block(0, true) .unwrap() .try_into_chain_block() .map(Arc::new) diff --git a/base_layer/core/src/chain_storage/db_transaction.rs b/base_layer/core/src/chain_storage/db_transaction.rs index e1fcbbcb6a..4f30c2687c 100644 --- a/base_layer/core/src/chain_storage/db_transaction.rs +++ b/base_layer/core/src/chain_storage/db_transaction.rs @@ -202,6 +202,9 @@ impl DbTransaction { /// Insert a "chained" orphan block. /// The transaction will rollback and write will return an error if the orphan already exists. pub fn insert_chained_orphan(&mut self, orphan: Arc) -> &mut Self { + if orphan.block().body.inputs().iter().any(|b| b.is_compact()) { + panic!("Compact inputs are not supported in the blockchain"); + } self.operations.push(WriteOperation::InsertChainOrphanBlock(orphan)); self } diff --git a/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs b/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs index 8bf4c99128..93539a2583 100644 --- a/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs +++ b/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs @@ -683,7 +683,7 @@ impl LMDBDatabase { "deleted_txo_mmr_position_to_height_index", )?; - let hash = input.canonical_hash()?; + let hash = input.canonical_hash(); let key = InputKey::new(header_hash.as_slice(), mmr_position, hash.as_slice()); lmdb_insert( txn, diff --git a/base_layer/core/src/chain_storage/tests/blockchain_database.rs b/base_layer/core/src/chain_storage/tests/blockchain_database.rs index b32a7cd756..03b0910a6e 100644 --- a/base_layer/core/src/chain_storage/tests/blockchain_database.rs +++ b/base_layer/core/src/chain_storage/tests/blockchain_database.rs @@ -80,7 +80,7 @@ fn add_many_chained_blocks( ) -> (Vec>, Vec) { let last_header = db.fetch_last_header().unwrap(); let mut prev_block = db - .fetch_block(last_header.height) + .fetch_block(last_header.height, true) .unwrap() .try_into_block() .map(Arc::new) @@ -104,7 +104,7 @@ mod fetch_blocks { #[test] fn it_returns_genesis() { let db = setup(); - let blocks = db.fetch_blocks(0..).unwrap(); + let blocks = db.fetch_blocks(0.., true).unwrap(); assert_eq!(blocks.len(), 1); } @@ -112,7 +112,7 @@ mod fetch_blocks { fn it_returns_all() { let db = setup(); add_many_chained_blocks(4, &db); - let blocks = db.fetch_blocks(..).unwrap(); + let blocks = db.fetch_blocks(.., true).unwrap(); assert_eq!(blocks.len(), 5); for (i, item) in blocks.iter().enumerate().take(4 + 1) { assert_eq!(item.header().height, i as u64); @@ -123,7 +123,7 @@ mod fetch_blocks { fn it_returns_one() { let db = setup(); let (new_blocks, _) = add_many_chained_blocks(1, &db); - let blocks = db.fetch_blocks(1..=1).unwrap(); + let blocks = db.fetch_blocks(1..=1, true).unwrap(); assert_eq!(blocks.len(), 1); assert_eq!(blocks[0].block().hash(), new_blocks[0].hash()); } @@ -132,7 +132,7 @@ mod fetch_blocks { fn it_returns_nothing_if_asking_for_blocks_out_of_range() { let db = setup(); add_many_chained_blocks(1, &db); - let blocks = db.fetch_blocks(2..).unwrap(); + let blocks = db.fetch_blocks(2.., true).unwrap(); assert!(blocks.is_empty()); } @@ -140,7 +140,7 @@ mod fetch_blocks { fn it_returns_blocks_between_bounds_exclusive() { let db = setup(); add_many_chained_blocks(5, &db); - let blocks = db.fetch_blocks(3..5).unwrap(); + let blocks = db.fetch_blocks(3..5, true).unwrap(); assert_eq!(blocks.len(), 2); assert_eq!(blocks[0].header().height, 3); assert_eq!(blocks[1].header().height, 4); @@ -150,7 +150,7 @@ mod fetch_blocks { fn it_returns_blocks_between_bounds_inclusive() { let db = setup(); add_many_chained_blocks(5, &db); - let blocks = db.fetch_blocks(3..=5).unwrap(); + let blocks = db.fetch_blocks(3..=5, true).unwrap(); assert_eq!(blocks.len(), 3); assert_eq!(blocks[0].header().height, 3); assert_eq!(blocks[1].header().height, 4); @@ -161,7 +161,7 @@ mod fetch_blocks { fn it_returns_blocks_to_the_tip() { let db = setup(); add_many_chained_blocks(5, &db); - let blocks = db.fetch_blocks(3..).unwrap(); + let blocks = db.fetch_blocks(3.., true).unwrap(); assert_eq!(blocks.len(), 3); assert_eq!(blocks[0].header().height, 3); assert_eq!(blocks[1].header().height, 4); @@ -172,7 +172,7 @@ mod fetch_blocks { fn it_returns_blocks_from_genesis() { let db = setup(); add_many_chained_blocks(5, &db); - let blocks = db.fetch_blocks(..=3).unwrap(); + let blocks = db.fetch_blocks(..=3, true).unwrap(); assert_eq!(blocks.len(), 4); assert_eq!(blocks[0].header().height, 0); assert_eq!(blocks[1].header().height, 1); @@ -276,7 +276,7 @@ mod find_headers_after_hash { #[test] fn it_returns_from_genesis() { let db = setup(); - let genesis_hash = db.fetch_block(0).unwrap().block().hash(); + let genesis_hash = db.fetch_block(0, true).unwrap().block().hash(); add_many_chained_blocks(1, &db); let hashes = vec![genesis_hash]; let (index, headers) = db.find_headers_after_hash(hashes, 1).unwrap().unwrap(); @@ -291,12 +291,12 @@ mod find_headers_after_hash { add_many_chained_blocks(5, &db); let hashes = (1..=3) .rev() - .map(|i| db.fetch_block(i).unwrap().block().hash()) + .map(|i| db.fetch_block(i, true).unwrap().block().hash()) .collect::>(); let (index, headers) = db.find_headers_after_hash(hashes, 10).unwrap().unwrap(); assert_eq!(index, 0); assert_eq!(headers.len(), 2); - assert_eq!(&headers[0], db.fetch_block(4).unwrap().header()); + assert_eq!(&headers[0], db.fetch_block(4, true).unwrap().header()); } #[test] @@ -304,13 +304,13 @@ mod find_headers_after_hash { let db = setup(); add_many_chained_blocks(5, &db); let hashes = (2..=4) - .map(|i| db.fetch_block(i).unwrap().block().hash()) + .map(|i| db.fetch_block(i, true).unwrap().block().hash()) .chain(vec![FixedHash::zero(), FixedHash::zero()]) .rev(); let (index, headers) = db.find_headers_after_hash(hashes, 1).unwrap().unwrap(); assert_eq!(index, 2); assert_eq!(headers.len(), 1); - assert_eq!(&headers[0], db.fetch_block(5).unwrap().header()); + assert_eq!(&headers[0], db.fetch_block(5, true).unwrap().header()); } } @@ -487,7 +487,7 @@ mod prepare_new_block { #[test] fn it_errors_for_genesis_block() { let db = setup(); - let genesis = db.fetch_block(0).unwrap(); + let genesis = db.fetch_block(0, true).unwrap(); let template = NewBlockTemplate::from_block(genesis.block().clone(), Difficulty::min(), 5000 * T); let err = db.prepare_new_block(template).unwrap_err(); assert!(matches!(err, ChainStorageError::InvalidArguments { .. })); @@ -496,7 +496,7 @@ mod prepare_new_block { #[test] fn it_errors_for_non_tip_template() { let db = setup(); - let genesis = db.fetch_block(0).unwrap(); + let genesis = db.fetch_block(0, true).unwrap(); let next_block = BlockHeader::from_previous(genesis.header()); let mut template = NewBlockTemplate::from_block(next_block.into_builder().build(), Difficulty::min(), 5000 * T); // This would cause a panic if the sanity checks were not there @@ -511,7 +511,7 @@ mod prepare_new_block { #[test] fn it_prepares_the_first_block() { let db = setup(); - let genesis = db.fetch_block(0).unwrap(); + let genesis = db.fetch_block(0, true).unwrap(); let next_block = BlockHeader::from_previous(genesis.header()); let template = NewBlockTemplate::from_block(next_block.into_builder().build(), Difficulty::min(), 5000 * T); let block = db.prepare_new_block(template).unwrap(); @@ -525,7 +525,7 @@ mod fetch_header_containing_utxo_mmr { #[test] fn it_returns_genesis() { let db = setup(); - let genesis = db.fetch_block(0).unwrap(); + let genesis = db.fetch_block(0, true).unwrap(); assert!(!genesis.block().body.outputs().is_empty()); let mut mmr_position = 0; genesis.block().body.outputs().iter().for_each(|_| { @@ -540,7 +540,7 @@ mod fetch_header_containing_utxo_mmr { #[test] fn it_returns_corresponding_header() { let db = setup(); - let genesis = db.fetch_block(0).unwrap(); + let genesis = db.fetch_block(0, true).unwrap(); let _block_and_outputs = add_many_chained_blocks(5, &db); let num_genesis_outputs = genesis.block().body.outputs().len() as u64; @@ -565,7 +565,7 @@ mod fetch_header_containing_kernel_mmr { #[test] fn it_returns_genesis() { let db = setup(); - let genesis = db.fetch_block(0).unwrap(); + let genesis = db.fetch_block(0, true).unwrap(); assert_eq!(genesis.block().body.kernels().len(), 2); let mut mmr_position = 0; genesis.block().body.kernels().iter().for_each(|_| { @@ -580,7 +580,7 @@ mod fetch_header_containing_kernel_mmr { #[test] fn it_returns_corresponding_header() { let db = setup(); - let genesis = db.fetch_block(0).unwrap(); + let genesis = db.fetch_block(0, true).unwrap(); let (blocks, outputs) = add_many_chained_blocks(1, &db); let num_genesis_kernels = genesis.block().body.kernels().len() as u64; let (txns, _) = schema_to_transaction(&[txn_schema!(from: vec![outputs[0].clone()], to: vec![50 * T])]); @@ -627,7 +627,7 @@ mod clear_all_pending_headers { fn it_clears_headers_after_tip() { let db = setup(); let _blocks_and_outputs = add_many_chained_blocks(2, &db); - let prev_block = db.fetch_block(2).unwrap(); + let prev_block = db.fetch_block(2, true).unwrap(); let mut prev_accum = prev_block.accumulated_data.clone(); let mut prev_header = prev_block.try_into_chain_block().unwrap().to_chain_header(); let headers = (0..5) diff --git a/base_layer/core/src/test_helpers/blockchain.rs b/base_layer/core/src/test_helpers/blockchain.rs index 9572ac9ac2..072364f788 100644 --- a/base_layer/core/src/test_helpers/blockchain.rs +++ b/base_layer/core/src/test_helpers/blockchain.rs @@ -453,7 +453,12 @@ pub fn create_main_chain>( db: &BlockchainDatabase, blocks: T, ) -> (Vec, HashMap>) { - let genesis_block = db.fetch_block(0).unwrap().try_into_chain_block().map(Arc::new).unwrap(); + let genesis_block = db + .fetch_block(0, true) + .unwrap() + .try_into_chain_block() + .map(Arc::new) + .unwrap(); let (names, chain) = create_chained_blocks(blocks, genesis_block); names.iter().for_each(|name| { let block = chain.get(name).unwrap(); @@ -487,7 +492,12 @@ pub struct TestBlockchain { impl TestBlockchain { pub fn new(db: BlockchainDatabase, rules: ConsensusManager) -> Self { - let genesis = db.fetch_block(0).unwrap().try_into_chain_block().map(Arc::new).unwrap(); + let genesis = db + .fetch_block(0, true) + .unwrap() + .try_into_chain_block() + .map(Arc::new) + .unwrap(); let mut blockchain = Self { db, chain: Default::default(), diff --git a/base_layer/core/src/transactions/transaction_components/transaction_input.rs b/base_layer/core/src/transactions/transaction_components/transaction_input.rs index a83950b922..9c3e664bdf 100644 --- a/base_layer/core/src/transactions/transaction_components/transaction_input.rs +++ b/base_layer/core/src/transactions/transaction_components/transaction_input.rs @@ -365,14 +365,14 @@ impl TransactionInput { } /// Implement the canonical hashing function for TransactionInput for use in ordering - pub fn canonical_hash(&self) -> Result { + pub fn canonical_hash(&self) -> FixedHash { let writer = DomainSeparatedConsensusHasher::::new("transaction_input") .chain(&self.version) .chain(&self.script_signature) .chain(&self.input_data) .chain(&self.output_hash()); - Ok(writer.finalize().into()) + writer.finalize().into() } pub fn set_maturity(&mut self, maturity: u64) -> Result<(), TransactionError> { @@ -426,7 +426,7 @@ impl Display for TransactionInput { features, script, sender_offset_public_key.to_hex(), - self.canonical_hash().expect("unreachable: output data exists").to_hex(), + self.canonical_hash().to_hex(), self.output_hash().to_hex() ), } diff --git a/base_layer/core/tests/async_db.rs b/base_layer/core/tests/async_db.rs index fb8e2ee629..7c8bddba71 100644 --- a/base_layer/core/tests/async_db.rs +++ b/base_layer/core/tests/async_db.rs @@ -84,9 +84,9 @@ fn async_rewind_to_height() { let db = AsyncBlockchainDb::new(db); rt.spawn(async move { db.rewind_to_height(2).await.unwrap(); - let result = db.fetch_block(3).await; + let result = db.fetch_block(3, true).await; assert!(result.is_err()); - let block = db.fetch_block(2).await.unwrap(); + let block = db.fetch_block(2, true).await.unwrap(); assert_eq!(block.confirmations(), 1); assert_eq!(blocks[2].block(), block.block()); }); @@ -122,7 +122,7 @@ fn fetch_async_block() { rt.spawn(async move { for block in blocks { let height = block.height(); - let block_check = db.fetch_block(height).await.unwrap(); + let block_check = db.fetch_block(height, true).await.unwrap(); assert_eq!(block.block(), block_check.block()); } }); @@ -154,7 +154,7 @@ fn async_add_new_block() { let db = AsyncBlockchainDb::new(db); rt.spawn(async move { let result = db.add_block(new_block.clone().into()).await.unwrap(); - let block = db.fetch_block(1).await.unwrap(); + let block = db.fetch_block(1, true).await.unwrap(); match result { BlockAddResult::Ok(_) => assert_eq!(Block::from(block).hash(), new_block.hash()), _ => panic!("Unexpected result"), diff --git a/base_layer/core/tests/block_validation.rs b/base_layer/core/tests/block_validation.rs index 1c1e51eedc..9659a99b55 100644 --- a/base_layer/core/tests/block_validation.rs +++ b/base_layer/core/tests/block_validation.rs @@ -109,7 +109,7 @@ fn test_monero_blocks() { cm.clone(), Validators::new(MockValidator::new(true), header_validator, MockValidator::new(true)), ); - let block_0 = db.fetch_block(0).unwrap().try_into_chain_block().unwrap(); + let block_0 = db.fetch_block(0, true).unwrap().try_into_chain_block().unwrap(); let (block_1_t, _) = chain_block_with_new_coinbase(&block_0, vec![], &cm, &factories); let mut block_1 = db.prepare_new_block(block_1_t).unwrap(); diff --git a/base_layer/core/tests/chain_storage_tests/chain_storage.rs b/base_layer/core/tests/chain_storage_tests/chain_storage.rs index 91653f5590..d48528d038 100644 --- a/base_layer/core/tests/chain_storage_tests/chain_storage.rs +++ b/base_layer/core/tests/chain_storage_tests/chain_storage.rs @@ -136,7 +136,7 @@ fn store_and_retrieve_block() { assert_eq!(metadata.best_block(), hash); assert_eq!(metadata.horizon_block(metadata.height_of_longest_chain()), 0); // Fetch the block back - let block0 = db.fetch_block(0).unwrap(); + let block0 = db.fetch_block(0, true).unwrap(); assert_eq!(block0.confirmations(), 1); // Compare the blocks let block0 = Block::from(block0); @@ -151,7 +151,7 @@ fn add_multiple_blocks() { let store = create_store_with_consensus(consensus_manager.clone()); let metadata = store.get_chain_metadata().unwrap(); assert_eq!(metadata.height_of_longest_chain(), 0); - let block0 = store.fetch_block(0).unwrap(); + let block0 = store.fetch_block(0, true).unwrap(); assert_eq!(metadata.best_block(), block0.hash()); // Add another block let block1 = append_block( @@ -189,10 +189,10 @@ fn test_checkpoints() { let (txn, _) = spend_utxos(txn); let block1 = append_block(&db, &blocks[0], vec![txn], &consensus_manager, 1.into()).unwrap(); // Get the checkpoint - let block_a = db.fetch_block(0).unwrap(); + let block_a = db.fetch_block(0, false).unwrap(); assert_eq!(block_a.confirmations(), 2); assert_eq!(blocks[0].block(), block_a.block()); - let block_b = db.fetch_block(1).unwrap(); + let block_b = db.fetch_block(1, false).unwrap(); assert_eq!(block_b.confirmations(), 1); let block1 = serde_json::to_string(block1.block()).unwrap(); let block_b = serde_json::to_string(&Block::from(block_b)).unwrap(); @@ -258,7 +258,7 @@ fn test_coverage_chain_storage() { ) .unwrap(); - let block0 = store.fetch_block(0).unwrap(); + let block0 = store.fetch_block(0, true).unwrap(); append_block( &store, &block0.clone().try_into_chain_block().unwrap(), @@ -583,29 +583,29 @@ fn reorgs_should_update_orphan_tips() { // Block A1 let txs = vec![txn_schema!(from: vec![a_outputs[0][0].clone()], to: vec![50 * T])]; - assert!(generate_new_block_with_achieved_difficulty( + generate_new_block_with_achieved_difficulty( &mut a_store, &mut a_blocks, &mut a_outputs, txs, Difficulty::from(1), - &consensus_manager + &consensus_manager, ) - .is_ok()); + .unwrap(); store.add_block(a_blocks[1].to_arc_block()).unwrap().assert_added(); // Block A2 let txs = vec![txn_schema!(from: vec![a_outputs[1][1].clone()], to: vec![30 * T])]; - assert!(generate_new_block_with_achieved_difficulty( + generate_new_block_with_achieved_difficulty( &mut a_store, &mut a_blocks, &mut a_outputs, txs, Difficulty::from(3), - &consensus_manager + &consensus_manager, ) - .is_ok()); + .unwrap(); store.add_block(a_blocks[2].to_arc_block()).unwrap().assert_added(); let a2_hash = *a_blocks[2].hash(); @@ -617,15 +617,15 @@ fn reorgs_should_update_orphan_tips() { // Block B1 let txs = vec![txn_schema!(from: vec![b_outputs[0][0].clone()], to: vec![50 * T])]; - assert!(generate_new_block_with_achieved_difficulty( + generate_new_block_with_achieved_difficulty( &mut b_store, &mut b_blocks, &mut b_outputs, txs, Difficulty::from(2), - &consensus_manager + &consensus_manager, ) - .is_ok()); + .unwrap(); store.add_block(b_blocks[1].to_arc_block()).unwrap().assert_orphaned(); let b1_hash = *b_blocks[1].hash(); @@ -641,15 +641,15 @@ fn reorgs_should_update_orphan_tips() { // Block B2 let txs = vec![txn_schema!(from: vec![b_outputs[1][0].clone()], to: vec![40 * T])]; - assert!(generate_new_block_with_achieved_difficulty( + generate_new_block_with_achieved_difficulty( &mut b_store, &mut b_blocks, &mut b_outputs, txs, Difficulty::from(4), - &consensus_manager + &consensus_manager, ) - .is_ok()); + .unwrap(); store.add_block(b_blocks[2].to_arc_block()).unwrap().assert_reorg(2, 2); let b2_hash = *b_blocks[2].hash(); @@ -673,7 +673,7 @@ fn reorgs_should_update_orphan_tips() { // Block A3 let txs = vec![txn_schema!(from: vec![a_outputs[2][0].clone()], to: vec![25 * T])]; - assert!(generate_new_block_with_achieved_difficulty( + generate_new_block_with_achieved_difficulty( &mut a_store, &mut a_blocks, &mut a_outputs, @@ -681,7 +681,7 @@ fn reorgs_should_update_orphan_tips() { Difficulty::from(5), // A chain accumulated difficulty 9 &consensus_manager, ) - .is_ok()); + .unwrap(); store.add_block(a_blocks[3].to_arc_block()).unwrap().assert_reorg(3, 2); let a3_hash = *a_blocks[3].hash(); @@ -705,30 +705,30 @@ fn reorgs_should_update_orphan_tips() { // Block B3 let txs = vec![txn_schema!(from: vec![b_outputs[2][0].clone()], to: vec![30 * T])]; - assert!(generate_new_block_with_achieved_difficulty( + generate_new_block_with_achieved_difficulty( &mut b_store, &mut b_blocks, &mut b_outputs, txs, Difficulty::from(1), // B chain accumulated difficulty 7 - &consensus_manager + &consensus_manager, ) - .is_ok()); + .unwrap(); store.add_block(b_blocks[3].to_arc_block()).unwrap().assert_orphaned(); let b3_hash = *b_blocks[3].hash(); // Block B4 let txs = vec![txn_schema!(from: vec![b_outputs[3][0].clone()], to: vec![20 * T])]; - assert!(generate_new_block_with_achieved_difficulty( + generate_new_block_with_achieved_difficulty( &mut b_store, &mut b_blocks, &mut b_outputs, txs, Difficulty::from(5), // B chain accumulated difficulty 12 - &consensus_manager + &consensus_manager, ) - .is_ok()); + .unwrap(); store.add_block(b_blocks[4].to_arc_block()).unwrap().assert_reorg(4, 3); let b4_hash = *b_blocks[4].hash(); @@ -752,29 +752,29 @@ fn reorgs_should_update_orphan_tips() { // Block A4 let txs = vec![txn_schema!(from: vec![a_outputs[3][0].clone()], to: vec![20 * T])]; - assert!(generate_new_block_with_achieved_difficulty( + generate_new_block_with_achieved_difficulty( &mut a_store, &mut a_blocks, &mut a_outputs, txs, Difficulty::from(2), // A chain accumulated difficulty 11 - &consensus_manager + &consensus_manager, ) - .is_ok()); + .unwrap(); store.add_block(a_blocks[4].to_arc_block()).unwrap().assert_orphaned(); // Block A5 let txs = vec![txn_schema!(from: vec![a_outputs[4][0].clone()], to: vec![10 * T])]; - assert!(generate_new_block_with_achieved_difficulty( + generate_new_block_with_achieved_difficulty( &mut a_store, &mut a_blocks, &mut a_outputs, txs, Difficulty::from(4), // A chain accumulated difficulty 15 - &consensus_manager + &consensus_manager, ) - .is_ok()); + .unwrap(); store.add_block(a_blocks[5].to_arc_block()).unwrap().assert_reorg(5, 4); @@ -825,15 +825,15 @@ fn handle_reorg_with_no_removed_blocks() { from: vec![outputs[0][0].clone()], to: vec![10 * T, 10 * T, 10 * T, 10 * T] )]; - assert!(generate_new_block_with_achieved_difficulty( + generate_new_block_with_achieved_difficulty( &mut store, &mut blocks, &mut outputs, txs, Difficulty::from(1), - &consensus_manager + &consensus_manager, ) - .is_ok()); + .unwrap(); // Create Forked Chain 1 let mut orphan1_store = create_store_with_consensus(consensus_manager.clone()); @@ -842,29 +842,29 @@ fn handle_reorg_with_no_removed_blocks() { let mut orphan1_outputs = vec![outputs[0].clone(), outputs[1].clone()]; // Block B2 let txs = vec![txn_schema!(from: vec![orphan1_outputs[1][0].clone()], to: vec![5 * T])]; - assert!(generate_new_block_with_achieved_difficulty( + generate_new_block_with_achieved_difficulty( &mut orphan1_store, &mut orphan1_blocks, &mut orphan1_outputs, txs, Difficulty::from(1), - &consensus_manager + &consensus_manager, ) - .is_ok()); + .unwrap(); // Block B3 let txs = vec![ txn_schema!(from: vec![orphan1_outputs[1][3].clone()], to: vec![3 * T]), txn_schema!(from: vec![orphan1_outputs[2][0].clone()], to: vec![3 * T]), ]; - assert!(generate_new_block_with_achieved_difficulty( + generate_new_block_with_achieved_difficulty( &mut orphan1_store, &mut orphan1_blocks, &mut orphan1_outputs, txs, Difficulty::from(1), - &consensus_manager + &consensus_manager, ) - .is_ok()); + .unwrap(); // Now add the fork blocks B3 and B2 (out of order) to the first DB and ensure a reorg. // see https://github.com/tari-project/tari/issues/2101#issuecomment-679188619 @@ -1020,7 +1020,7 @@ fn store_and_retrieve_blocks() { ) .unwrap(); - let block0 = store.fetch_block(0).unwrap(); + let block0 = store.fetch_block(0, true).unwrap(); let block1 = append_block( &store, &block0.clone().try_into_chain_block().unwrap(), @@ -1031,20 +1031,35 @@ fn store_and_retrieve_blocks() { .unwrap(); let block2 = append_block(&store, &block1, vec![], &rules, 1.into()).unwrap(); assert_eq!( - store.fetch_block(0).unwrap().try_into_chain_block().unwrap(), + store.fetch_block(0, true).unwrap().try_into_chain_block().unwrap(), block0.clone().try_into_chain_block().unwrap() ); - assert_eq!(store.fetch_block(1).unwrap().try_into_chain_block().unwrap(), block1); - assert_eq!(store.fetch_block(2).unwrap().try_into_chain_block().unwrap(), block2); + assert_eq!( + store.fetch_block(1, true).unwrap().try_into_chain_block().unwrap(), + block1 + ); + assert_eq!( + store.fetch_block(2, true).unwrap().try_into_chain_block().unwrap(), + block2 + ); let block3 = append_block(&store, &block2, vec![], &rules, 1.into()).unwrap(); assert_eq!( - store.fetch_block(0).unwrap().try_into_chain_block().unwrap(), + store.fetch_block(0, true).unwrap().try_into_chain_block().unwrap(), block0.try_into_chain_block().unwrap() ); - assert_eq!(store.fetch_block(1).unwrap().try_into_chain_block().unwrap(), block1); - assert_eq!(store.fetch_block(2).unwrap().try_into_chain_block().unwrap(), block2); - assert_eq!(store.fetch_block(3).unwrap().try_into_chain_block().unwrap(), block3); + assert_eq!( + store.fetch_block(1, true).unwrap().try_into_chain_block().unwrap(), + block1 + ); + assert_eq!( + store.fetch_block(2, true).unwrap().try_into_chain_block().unwrap(), + block2 + ); + assert_eq!( + store.fetch_block(3, true).unwrap().try_into_chain_block().unwrap(), + block3 + ); } #[test] @@ -1184,8 +1199,8 @@ fn invalid_block() { let metadata = store.get_chain_metadata().unwrap(); assert_eq!(metadata.height_of_longest_chain(), 0); assert_eq!(metadata.best_block(), &block0_hash); - assert_eq!(store.fetch_block(0).unwrap().block().hash(), block0_hash); - assert!(store.fetch_block(1).is_err()); + assert_eq!(store.fetch_block(0, true).unwrap().block().hash(), block0_hash); + assert!(store.fetch_block(1, true).is_err()); // Block 1 let txs = vec![txn_schema!( @@ -1209,9 +1224,9 @@ fn invalid_block() { let metadata = store.get_chain_metadata().unwrap(); assert_eq!(metadata.height_of_longest_chain(), 1); assert_eq!(metadata.best_block(), &block1_hash); - assert_eq!(store.fetch_block(0).unwrap().hash(), &block0_hash); - assert_eq!(store.fetch_block(1).unwrap().hash(), &block1_hash); - assert!(store.fetch_block(2).is_err()); + assert_eq!(store.fetch_block(0, true).unwrap().hash(), &block0_hash); + assert_eq!(store.fetch_block(1, true).unwrap().hash(), &block1_hash); + assert!(store.fetch_block(2, true).is_err()); // Invalid Block 2 - Double spends genesis block output is_valid.set(false); @@ -1232,9 +1247,9 @@ fn invalid_block() { let metadata = store.get_chain_metadata().unwrap(); assert_eq!(metadata.height_of_longest_chain(), 1); assert_eq!(metadata.best_block(), &block1_hash); - assert_eq!(store.fetch_block(0).unwrap().hash(), &block0_hash); - assert_eq!(store.fetch_block(1).unwrap().hash(), &block1_hash); - assert!(store.fetch_block(2).is_err()); + assert_eq!(store.fetch_block(0, true).unwrap().hash(), &block0_hash); + assert_eq!(store.fetch_block(1, true).unwrap().hash(), &block1_hash); + assert!(store.fetch_block(2, true).is_err()); // Valid Block 2 is_valid.set(true); @@ -1256,10 +1271,10 @@ fn invalid_block() { let metadata = store.get_chain_metadata().unwrap(); assert_eq!(metadata.height_of_longest_chain(), 2); assert_eq!(metadata.best_block(), block2_hash); - assert_eq!(store.fetch_block(0).unwrap().hash(), &block0_hash); - assert_eq!(store.fetch_block(1).unwrap().hash(), &block1_hash); - assert_eq!(store.fetch_block(2).unwrap().hash(), block2_hash); - assert!(store.fetch_block(3).is_err()); + assert_eq!(store.fetch_block(0, true).unwrap().hash(), &block0_hash); + assert_eq!(store.fetch_block(1, true).unwrap().hash(), &block1_hash); + assert_eq!(store.fetch_block(2, true).unwrap().hash(), block2_hash); + assert!(store.fetch_block(3, true).is_err()); } #[test] diff --git a/base_layer/core/tests/node_comms_interface.rs b/base_layer/core/tests/node_comms_interface.rs index 8c23162d6e..cff3e3c558 100644 --- a/base_layer/core/tests/node_comms_interface.rs +++ b/base_layer/core/tests/node_comms_interface.rs @@ -83,7 +83,7 @@ async fn inbound_get_metadata() { outbound_nci, connectivity, ); - let block = store.fetch_block(0).unwrap().block().clone(); + let block = store.fetch_block(0, true).unwrap().block().clone(); if let Ok(NodeCommsResponse::ChainMetadata(received_metadata)) = inbound_nch.handle_request(NodeCommsRequest::GetChainMetadata).await @@ -116,7 +116,7 @@ async fn inbound_fetch_kernel_by_excess_sig() { outbound_nci, connectivity, ); - let block = store.fetch_block(0).unwrap().block().clone(); + let block = store.fetch_block(0, true).unwrap().block().clone(); let sig = block.body.kernels()[0].excess_sig.clone(); if let Ok(NodeCommsResponse::TransactionKernels(received_kernels)) = inbound_nch @@ -149,7 +149,7 @@ async fn inbound_fetch_headers() { outbound_nci, connectivity, ); - let header = store.fetch_block(0).unwrap().header().clone(); + let header = store.fetch_block(0, true).unwrap().header().clone(); if let Ok(NodeCommsResponse::BlockHeaders(received_headers)) = inbound_nch.handle_request(NodeCommsRequest::FetchHeaders(0..=0)).await @@ -182,7 +182,7 @@ async fn inbound_fetch_utxos() { outbound_nci, connectivity, ); - let block = store.fetch_block(0).unwrap().block().clone(); + let block = store.fetch_block(0, true).unwrap().block().clone(); let utxo_1 = block.body.outputs()[0].clone(); let hash_1 = utxo_1.hash(); @@ -227,10 +227,13 @@ async fn inbound_fetch_blocks() { outbound_nci, connectivity, ); - let block = store.fetch_block(0).unwrap().block().clone(); + let block = store.fetch_block(0, true).unwrap().block().clone(); if let Ok(NodeCommsResponse::HistoricalBlocks(received_blocks)) = inbound_nch - .handle_request(NodeCommsRequest::FetchMatchingBlocks(0..=0)) + .handle_request(NodeCommsRequest::FetchMatchingBlocks { + range: 0..=0, + compact: true, + }) .await { assert_eq!(received_blocks.len(), 1); @@ -328,7 +331,10 @@ async fn inbound_fetch_blocks_before_horizon_height() { let _block5 = append_block(&store, &block4, vec![], &consensus_manager, 1.into()).unwrap(); if let Ok(NodeCommsResponse::HistoricalBlocks(received_blocks)) = inbound_nch - .handle_request(NodeCommsRequest::FetchMatchingBlocks(1..=1)) + .handle_request(NodeCommsRequest::FetchMatchingBlocks { + range: 1..=1, + compact: true, + }) .await { assert_eq!(received_blocks.len(), 1); @@ -338,7 +344,10 @@ async fn inbound_fetch_blocks_before_horizon_height() { } if let Ok(NodeCommsResponse::HistoricalBlocks(received_blocks)) = inbound_nch - .handle_request(NodeCommsRequest::FetchMatchingBlocks(2..=2)) + .handle_request(NodeCommsRequest::FetchMatchingBlocks { + range: 2..=2, + compact: true, + }) .await { assert_eq!(received_blocks.len(), 1); diff --git a/base_layer/core/tests/node_service.rs b/base_layer/core/tests/node_service.rs index 6a13166ff0..a3032a6ee2 100644 --- a/base_layer/core/tests/node_service.rs +++ b/base_layer/core/tests/node_service.rs @@ -57,9 +57,9 @@ use tempfile::tempdir; use crate::helpers::block_builders::{construct_chained_blocks, create_coinbase}; -#[allow(dead_code)] mod helpers; +#[allow(clippy::too_many_lines)] #[tokio::test] async fn propagate_and_forward_many_valid_blocks() { let temp_dir = tempdir().unwrap(); @@ -81,7 +81,12 @@ async fn propagate_and_forward_many_valid_blocks() { let consensus_constants = ConsensusConstantsBuilder::new(network) .with_emission_amounts(100_000_000.into(), &EMISSION, 100.into()) .build(); - let (block0, _) = create_genesis_block(&factories, &consensus_constants); + let (block0, outputs) = create_genesis_block_with_utxos(&factories, &[T, T], &consensus_constants); + + let (tx01, _tx01_out) = spend_utxos( + txn_schema!(from: vec![outputs[1].clone()], to: vec![20_000 * uT], fee: 10*uT, lock: 0, features: OutputFeatures::default()), + ); + let rules = ConsensusManager::builder(network) .add_consensus_constants(consensus_constants) .with_block(block0.clone()) @@ -140,7 +145,14 @@ async fn propagate_and_forward_many_valid_blocks() { let mut carol_block_event_stream = carol_node.local_nci.get_block_event_stream(); let mut dan_block_event_stream = dan_node.local_nci.get_block_event_stream(); - let blocks = construct_chained_blocks(&alice_node.blockchain_db, block0, &rules, 5); + let mut blocks = Vec::with_capacity(6); + blocks.push(append_block(&alice_node.blockchain_db, &block0, vec![tx01], &rules, 1.into()).unwrap()); + blocks.extend(construct_chained_blocks( + &alice_node.blockchain_db, + blocks[0].clone(), + &rules, + 5, + )); for block in &blocks { alice_node @@ -178,6 +190,7 @@ async fn propagate_and_forward_many_valid_blocks() { carol_node.shutdown().await; dan_node.shutdown().await; } + static EMISSION: [u64; 2] = [10, 10]; #[tokio::test] async fn propagate_and_forward_invalid_block_hash() { @@ -423,7 +436,7 @@ async fn local_get_metadata() { .start(temp_dir.path().to_str().unwrap()) .await; let db = &node.blockchain_db; - let block0 = db.fetch_block(0).unwrap().try_into_chain_block().unwrap(); + let block0 = db.fetch_block(0, true).unwrap().try_into_chain_block().unwrap(); let block1 = append_block(db, &block0, vec![], &consensus_manager, 1.into()).unwrap(); let block2 = append_block(db, &block1, vec![], &consensus_manager, 1.into()).unwrap(); @@ -634,7 +647,7 @@ async fn local_submit_block() { let db = &node.blockchain_db; let mut event_stream = node.local_nci.get_block_event_stream(); - let block0 = db.fetch_block(0).unwrap().block().clone(); + let block0 = db.fetch_block(0, true).unwrap().block().clone(); let mut block1 = db .prepare_new_block(chain_block(&block0, vec![], &consensus_manager)) .unwrap();