-
Notifications
You must be signed in to change notification settings - Fork 129
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
d9ef537
commit 007a9a2
Showing
2 changed files
with
444 additions
and
55 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,389 @@ | ||
/* This file is part of DarkFi (https://dark.fi) | ||
* | ||
* Copyright (C) 2020-2023 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 <https://www.gnu.org/licenses/>. | ||
*/ | ||
|
||
use darkfi_serial::{deserialize, serialize}; | ||
|
||
use crate::{ | ||
consensus::{Block, Header, LeadInfo}, | ||
util::time::Timestamp, | ||
Error, Result, | ||
}; | ||
|
||
use darkfi_sdk::{ | ||
crypto::{MerkleNode}, | ||
pasta::pallas, | ||
}; | ||
|
||
const SLED_HEADER_TREE: &[u8] = b"_headers"; | ||
const SLED_BLOCK_TREE: &[u8] = b"_blocks"; | ||
const SLED_BLOCK_ORDER_TREE: &[u8] = b"_block_order"; | ||
|
||
/// The `HeaderStore` is a `sled` tree storing all the blockchain's blocks' headers | ||
/// where the key is the headers' hash, and value is the serialized header. | ||
#[derive(Clone)] | ||
pub struct HeaderStore(sled::Tree); | ||
|
||
impl HeaderStore { | ||
/// Opens a new or existing `HeaderStore` on the given sled database. | ||
pub fn new(db: &sled::Db, genesis_ts: Timestamp, genesis_data: blake3::Hash) -> Result<Self> { | ||
let tree = db.open_tree(SLED_HEADER_TREE)?; | ||
let store = Self(tree); | ||
|
||
// In case the store is empty, initialize it with the genesis header. | ||
if store.0.is_empty() { | ||
let genesis_header = Header::genesis_header(genesis_ts, genesis_data); | ||
store.insert(&[genesis_header])?; | ||
} | ||
|
||
Ok(store) | ||
} | ||
|
||
/// Insert a slice of [`Header`] into the blockstore. With sled, the | ||
/// operation is done as a batch. | ||
/// 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 batch = sled::Batch::default(); | ||
|
||
for header in headers { | ||
let serialized = serialize(header); | ||
let headerhash = blake3::hash(&serialized); | ||
batch.insert(headerhash.as_bytes(), serialized); | ||
ret.push(headerhash); | ||
} | ||
|
||
self.0.apply_batch(batch)?; | ||
Ok(ret) | ||
} | ||
|
||
/// Check if the headerstore contains a given headerhash. | ||
pub fn contains(&self, headerhash: &blake3::Hash) -> Result<bool> { | ||
Ok(self.0.contains_key(headerhash.as_bytes())?) | ||
} | ||
|
||
/// Fetch given headerhashes from the headerstore. | ||
/// The resulting vector contains `Option`, which is `Some` if the header | ||
/// was found in the headerstore, 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()); | ||
|
||
for hash in headerhashes { | ||
if let Some(found) = self.0.get(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) | ||
} | ||
|
||
/// Retrieve all headers from the headerstore in the form of a tuple | ||
/// (`headerhash`, `header`). | ||
/// Be careful as this will try to load everything in memory. | ||
pub fn get_all(&self) -> Result<Vec<(blake3::Hash, Header)>> { | ||
let mut headers = vec![]; | ||
|
||
for header in self.0.iter() { | ||
let (key, value) = header.unwrap(); | ||
let hash_bytes: [u8; 32] = key.as_ref().try_into().unwrap(); | ||
let header = deserialize(&value)?; | ||
headers.push((hash_bytes.into(), header)); | ||
} | ||
|
||
Ok(headers) | ||
} | ||
} | ||
|
||
/// The `BlockStore` is a `sled` tree storing all the blockchain's blocks | ||
/// where the key is the blocks' hash, and value is the serialized block. | ||
#[derive(Clone)] | ||
#[derive(Debug)] | ||
pub struct BlockStore(sled::Tree); | ||
|
||
impl BlockStore { | ||
/// Opens a new or existing `BlockStore` on the given sled database. | ||
pub fn new(db: &sled::Db, genesis_ts: Timestamp, genesis_data: blake3::Hash) -> Result<Self> { | ||
let tree = db.open_tree(SLED_BLOCK_TREE)?; | ||
let store = Self(tree); | ||
// In case the store is empty, initialize it with the genesis block. | ||
if store.0.is_empty() { | ||
let genesis_block = Block::genesis_block(genesis_ts, genesis_data); | ||
store.insert(&[genesis_block])?; | ||
} | ||
|
||
Ok(store) | ||
} | ||
|
||
/// Insert a slice of [`Block`] into the store. With sled, the | ||
/// operation is done as a batch. | ||
/// 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 batch = sled::Batch::default(); | ||
|
||
for block in blocks { | ||
let serialized = serialize(block); | ||
let blockhash = blake3::hash(&serialized); | ||
batch.insert(blockhash.as_bytes(), serialized); | ||
ret.push(blockhash); | ||
} | ||
|
||
self.0.apply_batch(batch)?; | ||
Ok(ret) | ||
} | ||
|
||
/// Check if the blockstore contains a given blockhash. | ||
pub fn contains(&self, blockhash: &blake3::Hash) -> Result<bool> { | ||
Ok(self.0.contains_key(blockhash.as_bytes())?) | ||
} | ||
|
||
/// Fetch given blockhashhashes from the blockstore. | ||
/// The resulting vector contains `Option`, which is `Some` if the block | ||
/// was found in the blockstore, 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, | ||
blockhashhashes: &[blake3::Hash], | ||
strict: bool, | ||
) -> Result<Vec<Option<Block>>> { | ||
let mut ret = Vec::with_capacity(blockhashhashes.len()); | ||
|
||
for hash in blockhashhashes { | ||
if let Some(found) = self.0.get(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) | ||
} | ||
|
||
/// Retrieve all blocks from the blockstore in the form of a tuple | ||
/// (`blockhash`, `block`). | ||
/// Be careful as this will try to load everything in memory. | ||
pub fn get_all(&self) -> Result<Vec<(blake3::Hash, Block)>> { | ||
let mut blocks = vec![]; | ||
|
||
for block in self.0.iter() { | ||
let (key, value) = block.unwrap(); | ||
let hash_bytes: [u8; 32] = key.as_ref().try_into().unwrap(); | ||
let block = deserialize(&value)?; | ||
blocks.push((hash_bytes.into(), block)); | ||
} | ||
|
||
Ok(blocks) | ||
} | ||
} | ||
|
||
/// The `BlockOrderStore` is a `sled` tree storing the order of the | ||
/// blockchain's slots, where the key is the slot uid, and the value is | ||
/// the blocks' hash. [`BlockStore`] can be queried with this hash. | ||
#[derive(Clone)] | ||
pub struct BlockOrderStore(sled::Tree); | ||
|
||
impl BlockOrderStore { | ||
/// Opens a new or existing `BlockOrderStore` on the given sled database. | ||
pub fn new(db: &sled::Db, genesis_ts: Timestamp, genesis_data: blake3::Hash) -> Result<Self> { | ||
let tree = db.open_tree(SLED_BLOCK_ORDER_TREE)?; | ||
let store = Self(tree); | ||
|
||
// In case the store is empty, initialize it with the genesis block. | ||
if store.0.is_empty() { | ||
let genesis_block = Block::genesis_block(genesis_ts, genesis_data); | ||
store.insert(&[0], &[genesis_block.blockhash()])?; | ||
} | ||
|
||
Ok(store) | ||
} | ||
|
||
/// 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<()> { | ||
assert_eq!(slots.len(), hashes.len()); | ||
let mut batch = sled::Batch::default(); | ||
|
||
for (i, sl) in slots.iter().enumerate() { | ||
batch.insert(&sl.to_be_bytes(), hashes[i].as_bytes()); | ||
} | ||
|
||
self.0.apply_batch(batch)?; | ||
Ok(()) | ||
} | ||
|
||
/// Check if the blockorderstore contains a given slot. | ||
pub fn contains(&self, slot: u64) -> Result<bool> { | ||
Ok(self.0.contains_key(slot.to_be_bytes())?) | ||
} | ||
|
||
/// Fetch given slots from the blockorderstore. | ||
/// The resulting vector contains `Option`, which is `Some` if the slot | ||
/// was found in the blockstore, 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()); | ||
|
||
for slot in slots { | ||
if let Some(found) = self.0.get(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::SlotNotFound(*slot)) | ||
} | ||
ret.push(None); | ||
} | ||
} | ||
|
||
Ok(ret) | ||
} | ||
|
||
/// Retrieve all slots from the blockorderstore in the form of a tuple | ||
/// (`slot`, `blockhash`). | ||
/// Be careful as this will try to load everything in memory. | ||
pub fn get_all(&self) -> Result<Vec<(u64, blake3::Hash)>> { | ||
let mut slots = vec![]; | ||
|
||
for slot in self.0.iter() { | ||
let (key, value) = slot.unwrap(); | ||
let slot_bytes: [u8; 8] = key.as_ref().try_into().unwrap(); | ||
let hash_bytes: [u8; 32] = value.as_ref().try_into().unwrap(); | ||
let slot = u64::from_be_bytes(slot_bytes); | ||
let hash = blake3::Hash::from(hash_bytes); | ||
slots.push((slot, hash)); | ||
} | ||
|
||
Ok(slots) | ||
} | ||
|
||
/// Fetch n hashes after given slot. In the iteration, if a slot is not | ||
/// found, the iteration stops and the function returns what it has found | ||
/// so far in the `BlockOrderStore`. | ||
pub fn get_after(&self, slot: u64, n: u64) -> Result<Vec<blake3::Hash>> { | ||
let mut ret = vec![]; | ||
|
||
let mut key = slot; | ||
let mut counter = 0; | ||
while counter <= n { | ||
if let Some(found) = self.0.get_gt(key.to_be_bytes())? { | ||
let key_bytes: [u8; 8] = found.0.as_ref().try_into().unwrap(); | ||
key = u64::from_be_bytes(key_bytes); | ||
let blockhash = deserialize(&found.1)?; | ||
ret.push(blockhash); | ||
counter += 1; | ||
continue | ||
} | ||
break | ||
} | ||
|
||
Ok(ret) | ||
} | ||
|
||
/// Fetch the last blockhash in the tree, based on the `Ord` | ||
/// implementation for `Vec<u8>`. This should not be able to | ||
/// fail because we initialize the store with the genesis block. | ||
pub fn get_last(&self) -> Result<(u64, blake3::Hash)> { | ||
let found = self.0.last()?.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)) | ||
} | ||
|
||
/// Retrieve records count | ||
pub fn len(&self) -> usize { | ||
self.0.len() | ||
} | ||
|
||
/// Check if sled contains any records | ||
pub fn is_empty(&self) -> bool { | ||
self.0.len() == 0 | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
// Note this useful idiom: importing names from outer (for mod tests) scope. | ||
use super::*; | ||
|
||
fn create_tmp_db() -> Result<BlockStore> { | ||
let db = sled::Config::new().temporary(true).open()?; | ||
return BlockStore::new(&db,Timestamp::current_time(),blake3::hash(b"unit-testing-blockstore")); | ||
} | ||
|
||
#[test] | ||
fn test_new() { | ||
let bs = create_tmp_db(); | ||
assert_eq!(bs.is_ok(), true); | ||
} | ||
|
||
#[test] | ||
fn test_insert() { | ||
let res = create_tmp_db(); | ||
let bs = res.unwrap(); | ||
let txs:Vec<blake3::Hash> = gen_hashes(3); | ||
let merkle = MerkleNode::new(pallas::Base::from(42)); | ||
let blk = Block::new(blake3::hash(b"some-block"),1,0,txs, merkle, LeadInfo::default()); | ||
let hres = bs.insert(&[blk]); | ||
let hh = hres.unwrap(); | ||
let first = hh[0]; | ||
|
||
let mut contains = bs.contains(&first); | ||
assert_eq!(contains.is_ok(), true); | ||
let badhash = blake3::hash(b"blabla"); | ||
contains = bs.contains(&badhash); | ||
assert_eq!(contains.is_ok(), true); | ||
let v=contains.unwrap(); | ||
assert_eq!(v, false); | ||
let g = bs.get(&[first], true); | ||
assert_eq!(g.is_ok(), true); | ||
} | ||
|
||
fn gen_hashes(num:u64) -> Vec<blake3::Hash> { | ||
let mut v:Vec<blake3::Hash> = Vec::new(); | ||
for n in 0..num { | ||
let h = blake3::hash(&n.to_be_bytes()); | ||
v.push(h); | ||
} | ||
return v; | ||
} | ||
} |
Oops, something went wrong.