/* This file is part of DarkFi (https://dark.fi) * * Copyright (C) 2020-2022 Dyne.org foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ use darkfi_serial::serialize; use log::debug; use crate::{ consensus::{Block, BlockInfo, SlotCheckpoint}, util::time::Timestamp, Error, Result, }; pub mod blockstore; pub use blockstore::{BlockOrderStore, BlockStore, HeaderStore}; pub mod slotcheckpointstore; pub use slotcheckpointstore::SlotCheckpointStore; pub mod nfstore; pub use nfstore::NullifierStore; pub mod rootstore; pub use rootstore::RootStore; pub mod txstore; pub use txstore::TxStore; pub mod contractstore; pub use contractstore::{ContractStateStore, WasmStore}; /// Structure holding all sled trees that define the concept of Blockchain. #[derive(Clone)] pub struct Blockchain { /// Main pointer to the sled db connection pub sled_db: sled::Db, /// Headers sled tree pub headers: HeaderStore, /// Blocks sled tree pub blocks: BlockStore, /// Block order sled tree pub order: BlockOrderStore, /// Slot checkpoints sled tree pub slot_checkpoints: SlotCheckpointStore, /// Transactions sled tree pub transactions: TxStore, /// Nullifiers sled tree pub nullifiers: NullifierStore, /// Merkle roots sled tree pub merkle_roots: RootStore, /// Contract states pub contracts: ContractStateStore, /// Wasm bincodes pub wasm_bincode: WasmStore, } impl Blockchain { /// Instantiate a new `Blockchain` with the given `sled` database. pub fn new(db: &sled::Db, genesis_ts: Timestamp, genesis_data: blake3::Hash) -> Result { let headers = HeaderStore::new(db, genesis_ts, genesis_data)?; let blocks = BlockStore::new(db, genesis_ts, genesis_data)?; let order = BlockOrderStore::new(db, genesis_ts, genesis_data)?; let slot_checkpoints = SlotCheckpointStore::new(db)?; let transactions = TxStore::new(db)?; let nullifiers = NullifierStore::new(db)?; let merkle_roots = RootStore::new(db)?; let contracts = ContractStateStore::new(db)?; let wasm_bincode = WasmStore::new(db)?; Ok(Self { sled_db: db.clone(), headers, blocks, order, slot_checkpoints, transactions, nullifiers, merkle_roots, contracts, wasm_bincode, }) } /// Insert a given slice of [`BlockInfo`] into the blockchain database. /// This functions wraps all the logic of separating the block into specific /// data that can be fed into the different trees of the database. /// Upon success, the functions returns a vector of the block hashes that /// were given and appended to the ledger. pub fn add(&self, blocks: &[BlockInfo]) -> Result> { let mut ret = Vec::with_capacity(blocks.len()); // TODO: Make db writes here completely atomic for block in blocks { // 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 blockhash = self.blocks.insert(&[blk])?; ret.push(blockhash[0]); // Store block order self.order.insert(&[block.header.slot], &[blockhash[0]])?; } Ok(ret) } /// Check if the given [`BlockInfo`] is in the database and all trees. pub fn has_block(&self, block: &BlockInfo) -> Result { let blockhash = match self.order.get(&[block.header.slot], true) { Ok(v) => v[0].unwrap(), Err(_) => return Ok(false), }; // TODO: Check if we have all transactions // Check provided info produces the same hash Ok(blockhash == block.blockhash()) } /// Retrieve [`BlockInfo`]s by given hashes. Fails if any of them are not found. pub fn get_blocks_by_hash(&self, hashes: &[blake3::Hash]) -> Result> { let mut ret = Vec::with_capacity(hashes.len()); let blocks = self.blocks.get(hashes, true)?; for block in blocks { let block = block.unwrap(); 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 info = BlockInfo::new(header, txs, block.lead_info.clone()); ret.push(info); } Ok(ret) } /// Retrieve [`BlockInfo`]s by given slots. Does not fail if any of them are not found. pub fn get_blocks_by_slot(&self, slots: &[u64]) -> Result> { debug!("get_blocks_by_slot(): {:?}", slots); let blockhashes = self.order.get(slots, false)?; let mut hashes = vec![]; for i in blockhashes.into_iter().flatten() { hashes.push(i); } self.get_blocks_by_hash(&hashes) } /// Retrieve n blocks after given start slot. pub fn get_blocks_after(&self, slot: u64, n: u64) -> Result> { debug!("get_blocks_after(): {} -> {}", slot, n); let hashes = self.order.get_after(slot, n)?; self.get_blocks_by_hash(&hashes) } /// Retrieve stored blocks count pub fn len(&self) -> usize { self.order.len() } pub fn is_empty(&self) -> bool { self.order.len() == 0 } /// Retrieve the last block slot and hash. pub fn last(&self) -> Result<(u64, blake3::Hash)> { self.order.get_last() } /// Retrieve last finalized block leader proof hash. pub fn get_last_proof_hash(&self) -> Result { let (_, hash) = self.last().unwrap(); let blocks = self.blocks.get(&[hash], true)?; // Since we used strict get, its safe to unwrap here let block = blocks[0].clone().unwrap(); let hash = blake3::hash(&serialize(&block.lead_info.proof)); Ok(hash) } pub fn get_proof_hash_by_slot(&self, slot: u64) -> Result { let blocks = self.get_blocks_by_slot(&[slot]).unwrap(); if blocks.is_empty() { return Err(Error::BlockNotFound("block not found".to_string())) } // Since we used strict get, its safe to unwrap here let block = blocks[0].clone(); let hash = blake3::hash(&serialize(&block.lead_info.proof)); Ok(hash) } /// Retrieve last finalized block slot offset pub fn get_last_offset(&self) -> Result<(u64, u64)> { let (slot, hash) = self.last().unwrap(); let blocks = self.blocks.get(&[hash], true)?; // Since we used strict get, its safe to unwrap here let block = blocks[0].clone().unwrap(); Ok((slot, block.lead_info.offset)) } /// Retrieve the last slot checkpoint. pub fn last_slot_checkpoint(&self) -> Result { self.slot_checkpoints.get_last() } /// Retrieve n checkpoints after given start slot. pub fn get_slot_checkpoints_after(&self, slot: u64, n: u64) -> Result> { debug!("get_slot_checkpoints_after(): {} -> {}", slot, n); self.slot_checkpoints.get_after(slot, n) } /// Insert a given slice of [`SlotCheckpoint`] into the blockchain database. pub fn add_slot_checkpoints(&self, slot_checkpoints: &[SlotCheckpoint]) -> Result<()> { self.slot_checkpoints.insert(slot_checkpoints) } /// Retrieve [`SlotCheckpoint`]s by given slots. Does not fail if any of them are not found. pub fn get_slot_checkpoints_by_slot( &self, slots: &[u64], ) -> Result>> { debug!("get_slot_checkpoints_by_slot(): {:?}", slots); self.slot_checkpoints.get(slots, true) } /// Check if the given [`SlotCheckpoint`] is in the database and all trees. pub fn has_slot_checkpoint(&self, slot_checkpoint: &SlotCheckpoint) -> Result { if let Err(_) = self.slot_checkpoints.get(&[slot_checkpoint.slot], true) { return Ok(false) } Ok(true) } }