Просмотр исходного кода

validator/blockchain: added rest overlays needed for validations

aggstam 3 лет назад
Родитель
Сommit
f37e05e0ec

+ 2 - 2
Cargo.lock

@@ -4431,9 +4431,9 @@ dependencies = [
 
 [[package]]
 name = "sled-overlay"
-version = "0.0.5"
+version = "0.0.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae2e01bcd2f83aa0326724654068cf9099f416664dd928d97934bea80e90273f"
+checksum = "f96699ee7573bf3d272f05c9c899d896c9d970d7d49900a82cb46cdb8812e2f9"
 dependencies = [
  "sled",
 ]

+ 1 - 1
Cargo.toml

@@ -134,7 +134,7 @@ sqlx = {version = "0.6.3", features = ["runtime-async-std-rustls", "sqlite"], op
 
 # Blockchain store
 sled = {version = "0.34.7", optional = true}
-sled-overlay = {version = "0.0.5", optional = true}
+sled-overlay = {version = "0.0.7", optional = true}
 
 [dev-dependencies]
 clap = {version = "4.3.3", features = ["derive"]}

+ 182 - 2
src/validator/blockchain/block_store.rs

@@ -21,7 +21,7 @@ use darkfi_serial::{deserialize, serialize, SerialDecodable, SerialEncodable};
 
 use crate::{tx::Transaction, util::time::Timestamp, Error, Result};
 
-use super::Header;
+use super::{Header, SledDbOverlayPtr};
 
 /// Block version number
 pub const BLOCK_VERSION: u8 = 1;
@@ -117,6 +117,63 @@ impl BlockInfo {
         let block: Block = self.clone().into();
         block.blockhash()
     }
+
+    /// A block is considered valid when its parent hash is equal to the hash of the
+    /// previous block and their slots are incremental.
+    /// Additional validity rules can be applied.
+    pub fn validate(&self, previous: &Self) -> Result<()> {
+        let error = Err(Error::BlockIsInvalid(self.blockhash().to_string()));
+        let previous_hash = previous.blockhash();
+
+        // Check previous hash
+        if self.header.previous != previous_hash {
+            return error
+        }
+
+        // Check timestamps are incremental
+        if self.header.timestamp <= previous.header.timestamp {
+            return error
+        }
+
+        // Check slots are incremental
+        if self.header.slot <= previous.header.slot {
+            return error
+        }
+
+        // Verify slots exist
+        let mut slots = self.slots.clone();
+        if slots.is_empty() {
+            return error
+        }
+
+        // Sort them just to be safe
+        slots.sort_by(|a, b| b.id.cmp(&a.id));
+
+        // Verify first slot increments from previous block
+        if slots[0].id <= previous.header.slot {
+            return error
+        }
+
+        // Check all slot cover same sequence
+        for slot in &slots {
+            if !slot.fork_hashes.contains(&previous_hash) {
+                return error
+            }
+            if !slot.fork_previous_hashes.contains(&previous.header.previous) {
+                return error
+            }
+        }
+
+        // Check block slot is the last slot in the slice
+        if slots.last().unwrap().id != self.header.slot {
+            return error
+        }
+
+        // TODO: also validate slots etas and sigmas if we can derive them
+        // from previous slots
+
+        Ok(())
+    }
 }
 
 impl From<BlockInfo> for Block {
@@ -220,6 +277,59 @@ impl BlockStore {
     }
 }
 
+/// Overlay structure over a [`BlockStore`] instance.
+pub struct BlockStoreOverlay(SledDbOverlayPtr);
+
+impl BlockStoreOverlay {
+    pub fn new(overlay: SledDbOverlayPtr) -> Result<Self> {
+        overlay.lock().unwrap().open_tree(SLED_BLOCK_TREE)?;
+        Ok(Self(overlay))
+    }
+
+    /// Insert a slice of [`Block`] into the overlay.
+    /// The block are hashed with BLAKE3 and this blockhash is used as
+    /// the key, while value is the serialized [`Block`] itself.
+    /// On success, the function returns the block hashes in the same order.
+    pub fn insert(&self, blocks: &[Block]) -> Result<Vec<blake3::Hash>> {
+        let mut ret = Vec::with_capacity(blocks.len());
+        let mut lock = self.0.lock().unwrap();
+
+        for block in blocks {
+            let serialized = serialize(block);
+            let blockhash = blake3::hash(&serialized);
+            lock.insert(SLED_BLOCK_TREE, blockhash.as_bytes(), &serialized)?;
+            ret.push(blockhash);
+        }
+
+        Ok(ret)
+    }
+
+    /// Fetch given block hashes from the overlay.
+    /// The resulting vector contains `Option`, which is `Some` if the block
+    /// was found in the overlay, and otherwise it is `None`, if it has not.
+    /// The second parameter is a boolean which tells the function to fail in
+    /// case at least one block was not found.
+    pub fn get(&self, block_hashes: &[blake3::Hash], strict: bool) -> Result<Vec<Option<Block>>> {
+        let mut ret = Vec::with_capacity(block_hashes.len());
+        let lock = self.0.lock().unwrap();
+
+        for hash in block_hashes {
+            if let Some(found) = lock.get(SLED_BLOCK_TREE, hash.as_bytes())? {
+                let block = deserialize(&found)?;
+                ret.push(Some(block));
+            } else {
+                if strict {
+                    let s = hash.to_hex().as_str().to_string();
+                    return Err(Error::BlockNotFound(s))
+                }
+                ret.push(None);
+            }
+        }
+
+        Ok(ret)
+    }
+}
+
 /// Auxiliary structure used to keep track of blocks order.
 #[derive(Debug, SerialEncodable, SerialDecodable)]
 pub struct BlockOrder {
@@ -359,7 +469,77 @@ impl BlockOrderStore {
 
     /// Check if sled contains any records
     pub fn is_empty(&self) -> bool {
-        self.0.len() == 0
+        self.0.is_empty()
+    }
+}
+
+/// Overlay structure over a [`BlockOrderStore`] instance.
+pub struct BlockOrderStoreOverlay(SledDbOverlayPtr);
+
+impl BlockOrderStoreOverlay {
+    pub fn new(overlay: SledDbOverlayPtr) -> Result<Self> {
+        overlay.lock().unwrap().open_tree(SLED_BLOCK_ORDER_TREE)?;
+        Ok(Self(overlay))
+    }
+
+    /// Insert a slice of slots and blockhashes into the store. With sled, the
+    /// operation is done as a batch.
+    /// The block slot is used as the key, and the blockhash is used as value.
+    pub fn insert(&self, slots: &[u64], hashes: &[blake3::Hash]) -> Result<()> {
+        if slots.len() != hashes.len() {
+            return Err(Error::InvalidInputLengths)
+        }
+
+        let mut lock = self.0.lock().unwrap();
+
+        for (i, sl) in slots.iter().enumerate() {
+            lock.insert(SLED_BLOCK_ORDER_TREE, &sl.to_be_bytes(), hashes[i].as_bytes())?;
+        }
+
+        Ok(())
+    }
+
+    /// Fetch given slots from the overlay.
+    /// The resulting vector contains `Option`, which is `Some` if the slot
+    /// was found in the overlay, and otherwise it is `None`, if it has not.
+    /// The second parameter is a boolean which tells the function to fail in
+    /// case at least one slot was not found.
+    pub fn get(&self, slots: &[u64], strict: bool) -> Result<Vec<Option<blake3::Hash>>> {
+        let mut ret = Vec::with_capacity(slots.len());
+        let lock = self.0.lock().unwrap();
+
+        for slot in slots {
+            if let Some(found) = lock.get(SLED_BLOCK_ORDER_TREE, &slot.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::BlockSlotNotFound(*slot))
+                }
+                ret.push(None);
+            }
+        }
+
+        Ok(ret)
+    }
+
+    /// Fetch the last blockhash in the overlay, based on the `Ord`
+    /// implementation for `Vec<u8>`.
+    pub fn get_last(&self) -> Result<(u64, blake3::Hash)> {
+        let found = self.0.lock().unwrap().last(SLED_BLOCK_ORDER_TREE)?.unwrap();
+
+        let slot_bytes: [u8; 8] = found.0.as_ref().try_into().unwrap();
+        let hash_bytes: [u8; 32] = found.1.as_ref().try_into().unwrap();
+        let slot = u64::from_be_bytes(slot_bytes);
+        let hash = blake3::Hash::from(hash_bytes);
+
+        Ok((slot, hash))
+    }
+
+    /// Check if overlay contains any records
+    pub fn is_empty(&self) -> Result<bool> {
+        Ok(self.0.lock().unwrap().is_empty(SLED_BLOCK_ORDER_TREE)?)
     }
 }
 

+ 54 - 1
src/validator/blockchain/header_store.rs

@@ -21,7 +21,7 @@ use darkfi_serial::{deserialize, serialize, SerialDecodable, SerialEncodable};
 
 use crate::{util::time::Timestamp, Error, Result};
 
-use super::block_store::BLOCK_VERSION;
+use super::{block_store::BLOCK_VERSION, SledDbOverlayPtr};
 
 /// This struct represents a tuple of the form (version, previous, epoch, slot, timestamp, merkle_root).
 #[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)]
@@ -164,3 +164,56 @@ impl HeaderStore {
         Ok(headers)
     }
 }
+
+/// Overlay structure over a [`HeaderStore`] instance.
+pub struct HeaderStoreOverlay(SledDbOverlayPtr);
+
+impl HeaderStoreOverlay {
+    pub fn new(overlay: SledDbOverlayPtr) -> Result<Self> {
+        overlay.lock().unwrap().open_tree(SLED_HEADER_TREE)?;
+        Ok(Self(overlay))
+    }
+
+    /// Insert a slice of [`Header`] into the overlay.
+    /// The headers are hashed with BLAKE3 and this headerhash is used as
+    /// the key, while value is the serialized [`Header`] itself.
+    /// On success, the function returns the header hashes in the same order.
+    pub fn insert(&self, headers: &[Header]) -> Result<Vec<blake3::Hash>> {
+        let mut ret = Vec::with_capacity(headers.len());
+        let mut lock = self.0.lock().unwrap();
+
+        for header in headers {
+            let serialized = serialize(header);
+            let headerhash = blake3::hash(&serialized);
+            lock.insert(SLED_HEADER_TREE, headerhash.as_bytes(), &serialized)?;
+            ret.push(headerhash);
+        }
+
+        Ok(ret)
+    }
+
+    /// Fetch given headerhashes from the overlay.
+    /// The resulting vector contains `Option`, which is `Some` if the header
+    /// was found in the overlay, and otherwise it is `None`, if it has not.
+    /// The second parameter is a boolean which tells the function to fail in
+    /// case at least one header was not found.
+    pub fn get(&self, headerhashes: &[blake3::Hash], strict: bool) -> Result<Vec<Option<Header>>> {
+        let mut ret = Vec::with_capacity(headerhashes.len());
+        let lock = self.0.lock().unwrap();
+
+        for hash in headerhashes {
+            if let Some(found) = lock.get(SLED_HEADER_TREE, hash.as_bytes())? {
+                let header = deserialize(&found)?;
+                ret.push(Some(header));
+            } else {
+                if strict {
+                    let s = hash.to_hex().as_str().to_string();
+                    return Err(Error::HeaderNotFound(s))
+                }
+                ret.push(None);
+            }
+        }
+
+        Ok(ret)
+    }
+}

+ 129 - 65
src/validator/blockchain/mod.rs

@@ -27,16 +27,18 @@ use darkfi_serial::serialize;
 use crate::{tx::Transaction, Error, Result};
 
 pub mod block_store;
-pub use block_store::{Block, BlockInfo, BlockOrderStore, BlockStore};
+pub use block_store::{
+    Block, BlockInfo, BlockOrderStore, BlockOrderStoreOverlay, BlockStore, BlockStoreOverlay,
+};
 
 pub mod header_store;
-pub use header_store::{Header, HeaderStore};
+pub use header_store::{Header, HeaderStore, HeaderStoreOverlay};
 
 pub mod slot_store;
 pub use slot_store::{SlotStore, SlotStoreOverlay};
 
 pub mod tx_store;
-pub use tx_store::{PendingTxOrderStore, PendingTxStore, TxStore};
+pub use tx_store::{PendingTxOrderStore, PendingTxStore, TxStore, TxStoreOverlay};
 
 pub mod contract_store;
 pub use contract_store::{
@@ -95,72 +97,15 @@ impl Blockchain {
         })
     }
 
-    /// A block is considered valid when its parent hash is equal to the hash of the
-    /// previous block and their slots are incremental.
-    /// Additional validity rules can be applied.
-    pub fn validate_block(&self, block: &BlockInfo, previous_block: &BlockInfo) -> Result<()> {
-        let error = Err(Error::BlockIsInvalid(block.blockhash().to_string()));
-        let previous_block_hash = previous_block.blockhash();
-
-        // Check previous hash
-        if block.header.previous != previous_block_hash {
-            return error
-        }
-
-        // Check timestamps are incremental
-        if block.header.timestamp <= previous_block.header.timestamp {
-            return error
-        }
-
-        // Check slots are incremental
-        if block.header.slot <= previous_block.header.slot {
-            return error
-        }
-
-        // Verify slots exist
-        let mut slots = block.slots.clone();
-        if slots.is_empty() {
-            return error
-        }
-
-        // Sort them just to be safe
-        slots.sort_by(|a, b| b.id.cmp(&a.id));
-
-        // Verify first slot increments from previous block
-        if slots[0].id <= previous_block.header.slot {
-            return error
-        }
-
-        // Check all slot cover same sequence
-        for slot in &slots {
-            if !slot.fork_hashes.contains(&previous_block_hash) {
-                return error
-            }
-            if !slot.fork_previous_hashes.contains(&previous_block.header.previous) {
-                return error
-            }
-        }
-
-        // Check block slot is the last slot in the slice
-        if slots.last().unwrap().id != block.header.slot {
-            return error
-        }
-
-        // TODO: also validate slots etas and sigmas if we can derive them
-        // from previous slots
-
-        Ok(())
-    }
-
     /// A blockchain is considered valid, when every block is valid,
     /// based on validate_block checks.
     /// Be careful as this will try to load everything in memory.
-    pub fn validate_chain(&self) -> Result<()> {
+    pub fn validate(&self) -> Result<()> {
         // We use block order store here so we have all blocks in order
         let blocks = self.order.get_all()?;
         for (index, block) in blocks[1..].iter().enumerate() {
-            let full_blocks = self.get_blocks_by_hash(&[block.1, blocks[index].1])?;
-            self.validate_block(&full_blocks[0], &full_blocks[1])?;
+            let full_blocks = self.get_blocks_by_hash(&[blocks[index].1, block.1])?;
+            full_blocks[1].validate(&full_blocks[0])?;
         }
 
         Ok(())
@@ -295,7 +240,7 @@ impl Blockchain {
 
     /// Check if blockchain contains any blocks
     pub fn is_empty(&self) -> bool {
-        self.order.len() == 0
+        self.order.is_empty()
     }
 
     /// Retrieve the last block slot and hash.
@@ -425,8 +370,16 @@ pub type BlockchainOverlayPtr = Arc<Mutex<BlockchainOverlay>>;
 pub struct BlockchainOverlay {
     /// Main [`sled_overlay::SledDbOverlay`] to the sled db connection
     pub overlay: SledDbOverlayPtr,
+    /// Headers overlay
+    pub headers: HeaderStoreOverlay,
+    /// Blocks overlay
+    pub blocks: BlockStoreOverlay,
+    /// Block order overlay
+    pub order: BlockOrderStoreOverlay,
     /// Slots overlay
     pub slots: SlotStoreOverlay,
+    /// Transactions overlay
+    pub transactions: TxStoreOverlay,
     /// Contract states overlay
     pub contracts: ContractStateStoreOverlay,
     /// Wasm bincodes overlay
@@ -437,11 +390,122 @@ impl BlockchainOverlay {
     /// Instantiate a new `BlockchainOverlay` over the given [`Blockchain`] instance.
     pub fn new(blockchain: &Blockchain) -> Result<BlockchainOverlayPtr> {
         let overlay = Arc::new(Mutex::new(sled_overlay::SledDbOverlay::new(&blockchain.sled_db)));
+        let headers = HeaderStoreOverlay::new(overlay.clone())?;
+        let blocks = BlockStoreOverlay::new(overlay.clone())?;
+        let order = BlockOrderStoreOverlay::new(overlay.clone())?;
         let slots = SlotStoreOverlay::new(overlay.clone())?;
+        let transactions = TxStoreOverlay::new(overlay.clone())?;
         let contracts = ContractStateStoreOverlay::new(overlay.clone())?;
         let wasm_bincode = WasmStoreOverlay::new(overlay.clone())?;
 
-        Ok(Arc::new(Mutex::new(Self { overlay, slots, contracts, wasm_bincode })))
+        Ok(Arc::new(Mutex::new(Self {
+            overlay,
+            headers,
+            blocks,
+            order,
+            slots,
+            transactions,
+            contracts,
+            wasm_bincode,
+        })))
+    }
+
+    /// Check if blockchain contains any blocks
+    pub fn is_empty(&self) -> Result<bool> {
+        self.order.is_empty()
+    }
+
+    /// Retrieve the last block slot and hash.
+    pub fn last(&self) -> Result<(u64, blake3::Hash)> {
+        self.order.get_last()
+    }
+
+    /// Retrieve the last block info.
+    pub fn last_block(&self) -> Result<BlockInfo> {
+        let (_, hash) = self.last()?;
+        Ok(self.get_blocks_by_hash(&[hash])?[0].clone())
+    }
+
+    /// Insert a given [`BlockInfo`] into the overlay.
+    /// This functions wraps all the logic of separating the block into specific
+    /// data that can be fed into the different trees of the overlay.
+    /// Upon success, the functions returns the block hash that
+    /// were given and appended to the overlay.
+    /// Since we are adding to the overlay, we don't need to exeucte
+    /// the writes atomically.
+    pub fn add_block(&self, block: &BlockInfo) -> Result<blake3::Hash> {
+        // Store transactions
+        self.transactions.insert(&block.txs)?;
+
+        // Store header
+        self.headers.insert(&[block.header.clone()])?;
+
+        // Store block
+        let blk: Block = Block::from(block.clone());
+        let block_hash = self.blocks.insert(&[blk])?[0];
+
+        // Store block order
+        self.order.insert(&[block.header.slot], &[block_hash])?;
+
+        // Store slot checkpoints
+        self.slots.insert(&block.slots)?;
+
+        Ok(block_hash)
+    }
+
+    /// Check if the given [`BlockInfo`] is in the database and all trees.
+    pub fn has_block(&self, block: &BlockInfo) -> Result<bool> {
+        let blockhash = match self.order.get(&[block.header.slot], true) {
+            Ok(v) => v[0].unwrap(),
+            Err(_) => return Ok(false),
+        };
+
+        // Check if we have all transactions
+        let txs: Vec<blake3::Hash> =
+            block.txs.iter().map(|x| blake3::hash(&serialize(x))).collect();
+        if self.transactions.get(&txs, true).is_err() {
+            return Ok(false)
+        }
+
+        // Check if we have all slots
+        let slots: Vec<u64> = block.slots.iter().map(|x| x.id).collect();
+        if self.slots.get(&slots, true).is_err() {
+            return Ok(false)
+        }
+
+        // Check provided info produces the same hash
+        Ok(blockhash == block.blockhash())
+    }
+
+    /// Retrieve [`BlockInfo`]s by given hashes. Fails if any of them is not found.
+    pub fn get_blocks_by_hash(&self, hashes: &[blake3::Hash]) -> Result<Vec<BlockInfo>> {
+        let blocks = self.blocks.get(hashes, true)?;
+        let blocks: Vec<Block> = blocks.iter().map(|x| x.clone().unwrap()).collect();
+        let ret = self.get_blocks_infos(&blocks)?;
+
+        Ok(ret)
+    }
+
+    /// Retrieve all [`BlockInfo`] for given slice of [`Block`].
+    /// Fails if any of them is not found
+    fn get_blocks_infos(&self, blocks: &[Block]) -> Result<Vec<BlockInfo>> {
+        let mut ret = Vec::with_capacity(blocks.len());
+        for block in blocks {
+            let headers = self.headers.get(&[block.header], true)?;
+            // Since we used strict get, its safe to unwrap here
+            let header = headers[0].clone().unwrap();
+
+            let txs = self.transactions.get(&block.txs, true)?;
+            let txs = txs.iter().map(|x| x.clone().unwrap()).collect();
+
+            let slots = self.slots.get(&block.slots, true)?;
+            let slots = slots.iter().map(|x| x.clone().unwrap()).collect();
+
+            let info = BlockInfo::new(header, txs, block.producer.clone(), slots);
+            ret.push(info);
+        }
+
+        Ok(ret)
     }
 
     /// Checkpoint overlay so we can revert to it, if needed.

+ 40 - 3
src/validator/blockchain/slot_store.rs

@@ -139,7 +139,7 @@ impl SlotStore {
     }
 
     pub fn is_empty(&self) -> bool {
-        self.0.len() == 0
+        self.0.is_empty()
     }
 }
 
@@ -152,11 +152,48 @@ impl SlotStoreOverlay {
         Ok(Self(overlay))
     }
 
-    /// Fetch given id from the slot store.
-    pub fn get(&self, id: u64) -> Result<Vec<u8>> {
+    /// Insert a slice of [`Slot`] into the overlay.
+    /// The slot id is used as the key, while value is the serialized [`Slot`] itself.
+    pub fn insert(&self, slots: &[Slot]) -> Result<()> {
+        let mut lock = self.0.lock().unwrap();
+
+        for slot in slots {
+            let serialized = serialize(slot);
+            lock.insert(SLED_SLOT_TREE, &slot.id.to_be_bytes(), &serialized)?;
+        }
+
+        Ok(())
+    }
+
+    /// Fetch slot from the overlay by id.
+    pub fn get_by_id(&self, id: u64) -> Result<Vec<u8>> {
         match self.0.lock().unwrap().get(SLED_SLOT_TREE, &id.to_be_bytes())? {
             Some(found) => Ok(found.to_vec()),
             None => Err(Error::SlotNotFound(id)),
         }
     }
+
+    /// Fetch given slots from the overlay.
+    /// The resulting vector contains `Option`, which is `Some` if the slot
+    /// was found in the overlay, and otherwise it is `None`, if it has not.
+    /// The second parameter is a boolean which tells the function to fail in
+    /// case at least one slot was not found.
+    pub fn get(&self, ids: &[u64], strict: bool) -> Result<Vec<Option<Slot>>> {
+        let mut ret = Vec::with_capacity(ids.len());
+        let lock = self.0.lock().unwrap();
+
+        for id in ids {
+            if let Some(found) = lock.get(SLED_SLOT_TREE, &id.to_be_bytes())? {
+                let slot = deserialize(&found)?;
+                ret.push(Some(slot));
+            } else {
+                if strict {
+                    return Err(Error::SlotNotFound(*id))
+                }
+                ret.push(None);
+            }
+        }
+
+        Ok(ret)
+    }
 }

+ 61 - 1
src/validator/blockchain/tx_store.rs

@@ -22,6 +22,8 @@ use darkfi_serial::{deserialize, serialize};
 
 use crate::{tx::Transaction, Error, Result};
 
+use super::SledDbOverlayPtr;
+
 const SLED_TX_TREE: &[u8] = b"_transactions";
 const SLED_PENDING_TX_TREE: &[u8] = b"_pending_transactions";
 const SLED_PENDING_TX_ORDER_TREE: &[u8] = b"_pending_transactions_order";
@@ -125,7 +127,65 @@ impl TxStore {
     }
 
     pub fn is_empty(&self) -> bool {
-        self.0.len() == 0
+        self.0.is_empty()
+    }
+}
+
+/// Overlay structure over a [`TxStore`] instance.
+pub struct TxStoreOverlay(SledDbOverlayPtr);
+
+impl TxStoreOverlay {
+    pub fn new(overlay: SledDbOverlayPtr) -> Result<Self> {
+        overlay.lock().unwrap().open_tree(SLED_TX_TREE)?;
+        Ok(Self(overlay))
+    }
+
+    /// Insert a slice of [`Transaction`] into the overlay.
+    /// The transactions are hashed with BLAKE3 and this hash is used as
+    /// the key, while the value is the serialized [`Transaction`] itself.
+    /// On success, the function returns the transaction hashes in the same
+    /// order as the input transactions.
+    pub fn insert(&self, transactions: &[Transaction]) -> Result<Vec<blake3::Hash>> {
+        let mut ret = Vec::with_capacity(transactions.len());
+        let mut lock = self.0.lock().unwrap();
+
+        for tx in transactions {
+            let serialized = serialize(tx);
+            let tx_hash = blake3::hash(&serialized);
+            lock.insert(SLED_TX_TREE, tx_hash.as_bytes(), &serialized)?;
+            ret.push(tx_hash);
+        }
+
+        Ok(ret)
+    }
+
+    /// Fetch given tx hashes from the overlay.
+    /// The resulting vector contains `Option`, which is `Some` if the tx
+    /// was found in the overlay, and otherwise it is `None`, if it has not.
+    /// The second parameter is a boolean which tells the function to fail in
+    /// case at least one block was not found.
+    pub fn get(
+        &self,
+        tx_hashes: &[blake3::Hash],
+        strict: bool,
+    ) -> Result<Vec<Option<Transaction>>> {
+        let mut ret = Vec::with_capacity(tx_hashes.len());
+        let lock = self.0.lock().unwrap();
+
+        for tx_hash in tx_hashes {
+            if let Some(found) = lock.get(SLED_TX_TREE, tx_hash.as_bytes())? {
+                let tx = deserialize(&found)?;
+                ret.push(Some(tx));
+            } else {
+                if strict {
+                    let s = tx_hash.to_hex().as_str().to_string();
+                    return Err(Error::TransactionNotFound(s))
+                }
+                ret.push(None);
+            }
+        }
+
+        Ok(ret)
     }
 }
 

+ 10 - 9
tests/blockchain.rs

@@ -17,7 +17,7 @@
  */
 
 use darkfi::{
-    validator::blockchain::{BlockInfo, Blockchain, Header},
+    validator::blockchain::{BlockInfo, Blockchain, BlockchainOverlay, Header},
     Error, Result,
 };
 use darkfi_sdk::{
@@ -43,8 +43,8 @@ impl Harness {
     }
 
     fn validate_chains(&self) -> Result<()> {
-        self.alice.validate_chain()?;
-        self.bob.validate_chain()?;
+        self.alice.validate()?;
+        self.bob.validate()?;
 
         assert_eq!(self.alice.len(), self.bob.len());
 
@@ -83,33 +83,34 @@ impl Harness {
 
     // This is what the validator will execute when it receives a block.
     fn add_blocks_to_chain(&self, blockchain: &Blockchain, blocks: &[BlockInfo]) -> Result<()> {
-        // TODO: Use an overlay to revert changes in case of errors
         // Create overlay
+        let blockchain_overlay = BlockchainOverlay::new(&blockchain)?;
+        let lock = blockchain_overlay.lock().unwrap();
 
         // When we insert genesis, chain is empty
-        let mut previous =
-            if !blockchain.is_empty() { Some(blockchain.last_block()?) } else { None };
+        let mut previous = if !lock.is_empty()? { Some(lock.last_block()?) } else { None };
 
         // Validate and insert each block
         for block in blocks {
             // Check if block already exists
-            if blockchain.has_block(block)? {
+            if lock.has_block(block)? {
                 return Err(Error::BlockAlreadyExists(block.blockhash().to_string()))
             }
 
             // This will be true for every insert, apart from genesis
             if let Some(p) = previous {
-                blockchain.validate_block(block, &p)?;
+                block.validate(&p)?;
             }
 
             // Insert block
-            blockchain.add_block(block)?;
+            lock.add_block(block)?;
 
             // Use last inserted block as next iteration previous
             previous = Some(block.clone());
         }
 
         // Write overlay
+        lock.overlay.lock().unwrap().apply()?;
 
         Ok(())
     }