diff --git a/src/blockchain/blockstore.rs b/src/blockchain/blockstore.rs index 826fb389d..bd509c05f 100644 --- a/src/blockchain/blockstore.rs +++ b/src/blockchain/blockstore.rs @@ -3,7 +3,7 @@ use sled::Batch; use crate::{ consensus2::{util::Timestamp, Block}, util::serial::{deserialize, serialize}, - Result, + Error, Result, }; const SLED_BLOCK_TREE: &[u8] = b"_blocks"; @@ -46,14 +46,18 @@ impl BlockStore { /// Fetch given blockhashes from the blockstore. /// The resulting vector contains `Option` which is `Some` if the block /// was found in the blockstore, and `None`, if it has not. - pub fn get(&self, blockhashes: &[blake3::Hash]) -> Result>> { - let mut ret: Vec> = Vec::with_capacity(blockhashes.len()); + pub fn get(&self, blockhashes: &[blake3::Hash], strict: bool) -> Result>> { + let mut ret = Vec::with_capacity(blockhashes.len()); for i in blockhashes { if let Some(found) = self.0.get(i.as_bytes())? { let block = deserialize(&found)?; ret.push(Some(block)); } else { + if strict { + let s = i.to_hex().as_str().to_string(); + return Err(Error::BlockNotFound(s)) + } ret.push(None); } } @@ -113,6 +117,26 @@ impl BlockOrderStore { Ok(()) } + /// Retrieve all hashes given slots. + pub fn get(&self, slots: &[u64], strict: bool) -> Result>> { + let mut ret = Vec::with_capacity(slots.len()); + + for i in slots { + if let Some(found) = self.0.get(i.to_be_bytes())? { + let hash_bytes: [u8; 32] = found.as_ref().try_into().unwrap(); + let hash = blake3::Hash::from(hash_bytes); + ret.push(Some(hash)); + } else { + if strict { + return Err(Error::SlotNotFound(*i)) + } + ret.push(None); + } + } + + Ok(ret) + } + /// Retrieve the last block hash in the tree, based on the Ord /// implementation for Vec. pub fn get_last(&self) -> Result> { diff --git a/src/blockchain/metadatastore.rs b/src/blockchain/metadatastore.rs index 62c0761f5..9ffc93273 100644 --- a/src/blockchain/metadatastore.rs +++ b/src/blockchain/metadatastore.rs @@ -3,7 +3,7 @@ use sled::Batch; use crate::{ consensus2::StreamletMetadata, util::serial::{deserialize, serialize}, - Result, + Error, Result, }; const SLED_STREAMLET_METADATA_TREE: &[u8] = b"_streamlet_metadata"; @@ -31,6 +31,30 @@ impl StreamletMetadataStore { Ok(()) } + /// Retrieve `StreamletMetadata` by given blockhashes. + pub fn get( + &self, + blockhashes: &[blake3::Hash], + strict: bool, + ) -> Result>> { + let mut ret = Vec::with_capacity(blockhashes.len()); + + for i in blockhashes { + if let Some(found) = self.0.get(i.as_bytes())? { + let sm = deserialize(&found)?; + ret.push(Some(sm)); + } else { + if strict { + let s = i.to_hex().as_str().to_string(); + return Err(Error::BlockMetadataNotFound(s)) + } + ret.push(None); + } + } + + Ok(ret) + } + /// Retrieve all `StreamletMetadata`. /// Be careful as this will try to lead everything in memory. pub fn get_all(&self) -> Result>> { diff --git a/src/blockchain/mod.rs b/src/blockchain/mod.rs index d4151fc0d..e83d6f19e 100644 --- a/src/blockchain/mod.rs +++ b/src/blockchain/mod.rs @@ -1,7 +1,7 @@ use std::io; use crate::{ - consensus2::{block::BlockProposal, util::Timestamp, Block}, + consensus2::{block::BlockInfo, util::Timestamp, Block, BlockProposal}, impl_vec, util::serial::{Decodable, Encodable, ReadExt, VarInt, WriteExt}, Result, @@ -49,31 +49,76 @@ impl Blockchain { Ok(Self { blocks, order, transactions, streamlet_metadata, nullifiers, merkle_roots }) } - /// Batch insert [`BlockProposal`]s. - pub fn add(&mut self, proposals: &[BlockProposal]) -> Result> { - // TODO: Engineer this function in a better way. - let mut ret = Vec::with_capacity(proposals.len()); + /// Batch insert [`BlockInfo`]s. + pub fn add(&mut self, blocks: &[BlockInfo]) -> Result> { + let mut ret = Vec::with_capacity(blocks.len()); - for prop in proposals { + for block in blocks { // Store transactions - let tx_hashes = self.transactions.insert(&prop.txs)?; + let tx_hashes = self.transactions.insert(&block.txs)?; // Store block - let block = - Block { st: prop.st, sl: prop.sl, txs: tx_hashes, metadata: prop.metadata.clone() }; - let blockhash = self.blocks.insert(&[block.clone()])?; + let _block = Block::new(block.st, block.sl, tx_hashes, block.metadata.clone()); + let blockhash = self.blocks.insert(&[_block])?; ret.push(blockhash[0]); // Store block order self.order.insert(&[block.sl], &[blockhash[0]])?; - // Store streamlet metadata - self.streamlet_metadata.insert(&[blockhash[0]], &[prop.sm.clone()])?; + // Store Streamlet metadata + self.streamlet_metadata.insert(&[blockhash[0]], &[block.sm.clone()])?; } Ok(ret) } + /// Retrieve blocks by given hashes. Fails if any of them are not found. + pub fn get_blocks_by_hash(&self, blockhashes: &[blake3::Hash]) -> Result> { + let mut ret = Vec::with_capacity(blockhashes.len()); + + let blocks = self.blocks.get(blockhashes, true)?; + let metadata = self.streamlet_metadata.get(blockhashes, true)?; + + for (i, block) in blocks.iter().enumerate() { + let block = block.clone().unwrap(); + let sm = metadata[i].clone().unwrap(); + + let txs = self.transactions.get(&block.txs, true)?; + let txs = txs.iter().map(|x| x.clone().unwrap()).collect(); + + let info = BlockInfo::new(block.st, block.sl, txs, block.metadata.clone(), sm); + ret.push(info); + } + + Ok(ret) + } + + /// Retrieve blocks by given slots. Fails if any of them are not found. + pub fn get_blocks_by_slot(&self, slots: &[u64]) -> Result> { + let blockhashes = self.order.get(slots, true)?; + let blockhashes: Vec = blockhashes.iter().map(|x| x.unwrap()).collect(); + self.get_blocks_by_hash(&blockhashes) + } + + /// Check if the given [`BlockInfo`] is in the database + pub fn has_block(&self, info: &BlockInfo) -> Result { + let hashes = self.order.get(&[info.sl], true)?; + if hashes.is_empty() { + return Ok(false) + } + + if let Some(found) = &hashes[0] { + // Check provided info produces the same hash + // TODO: This BlockProposal::to_proposal_hash function should be in a better place. + let blockhash = + BlockProposal::to_proposal_hash(info.st, info.sl, &info.txs, &info.metadata); + + return Ok(&blockhash == found) + } + + Ok(false) + } + /// Retrieve the last block slot and hash pub fn last(&self) -> Result> { self.order.get_last() diff --git a/src/blockchain/txstore.rs b/src/blockchain/txstore.rs index a49b8d36a..8fbd25e25 100644 --- a/src/blockchain/txstore.rs +++ b/src/blockchain/txstore.rs @@ -3,7 +3,7 @@ use sled::Batch; use crate::{ consensus2::Tx, util::serial::{deserialize, serialize}, - Result, + Error, Result, }; const SLED_TX_TREE: &[u8] = b"_transactions"; @@ -35,6 +35,27 @@ impl TxStore { Ok(ret) } + /// Fetch requested transactions from the txstore. The `strict` param + /// will make the function fail if a transaction has not been found. + pub fn get(&self, tx_hashes: &[blake3::Hash], strict: bool) -> Result>> { + let mut ret: Vec> = Vec::with_capacity(tx_hashes.len()); + + for i in tx_hashes { + if let Some(found) = self.0.get(i.as_bytes())? { + let tx = deserialize(&found)?; + ret.push(Some(tx)); + } else { + if strict { + let s = i.to_hex().as_str().to_string(); + return Err(Error::TransactionNotFound(s)) + } + ret.push(None); + } + } + + Ok(ret) + } + /// Retrieve all transactions. /// Be careful as this will try to load everything in memory. pub fn get_all(&self) -> Result>> { diff --git a/src/error.rs b/src/error.rs index 6abad9b1b..4df4afe84 100644 --- a/src/error.rs +++ b/src/error.rs @@ -184,6 +184,18 @@ pub enum Error { #[error(transparent)] SledError(#[from] sled::Error), + #[error("Transaction {0} not found in database")] + TransactionNotFound(String), + + #[error("Block {0} not found in database")] + BlockNotFound(String), + + #[error("Block in slot {0} not found in database")] + SlotNotFound(u64), + + #[error("Block {0} metadata not found in database")] + BlockMetadataNotFound(String), + // ============= // Wallet errors // =============