/* 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 .
*/
use darkfi_sdk::{
blockchain::block_version,
crypto::MerkleTree,
pasta::{group::ff::Field, pallas},
};
#[cfg(feature = "async-serial")]
use darkfi_serial::async_trait;
use darkfi_serial::{deserialize, serialize, Encodable, SerialDecodable, SerialEncodable};
use crate::{util::time::Timestamp, Error, Result};
use super::{parse_record, SledDbOverlayPtr};
/// This struct represents a tuple of the form (version, previous, epoch, height, timestamp, nonce, merkle_root).
#[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)]
pub struct Header {
/// Block version
pub version: u8,
/// Previous block hash
pub previous: blake3::Hash,
/// Epoch number
pub epoch: u64,
/// Block/Slot height
pub height: u64,
/// Block creation timestamp
pub timestamp: Timestamp,
/// The block's nonce.
/// In PoW, this value changes arbitrarily with mining.
/// In PoS, we can use this value as our block producer ETA.
pub nonce: pallas::Base,
/// Merkle tree of the transactions contained in this block
pub tree: MerkleTree,
}
impl Header {
pub fn new(
previous: blake3::Hash,
epoch: u64,
height: u64,
timestamp: Timestamp,
nonce: pallas::Base,
) -> Self {
let version = block_version(height);
let tree = MerkleTree::new(1);
Self { version, previous, epoch, height, timestamp, nonce, tree }
}
/// Compute the header's hash
pub fn hash(&self) -> Result {
let mut hasher = blake3::Hasher::new();
self.version.encode(&mut hasher)?;
self.previous.encode(&mut hasher)?;
self.epoch.encode(&mut hasher)?;
self.height.encode(&mut hasher)?;
self.timestamp.encode(&mut hasher)?;
self.nonce.encode(&mut hasher)?;
self.tree.root(0).unwrap().encode(&mut hasher)?;
Ok(hasher.finalize())
}
}
impl Default for Header {
/// Represents the genesis header on current timestamp
fn default() -> Self {
Header::new(
blake3::hash(b"Let there be dark!"),
0,
0,
Timestamp::current_time(),
pallas::Base::ZERO,
)
}
}
/// [`Header`] sled tree
const SLED_HEADER_TREE: &[u8] = b"_headers";
/// 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(pub sled::Tree);
impl HeaderStore {
/// Opens a new or existing `HeaderStore` on the given sled database.
pub fn new(db: &sled::Db) -> Result {
let tree = db.open_tree(SLED_HEADER_TREE)?;
Ok(Self(tree))
}
/// Insert a slice of [`Header`] into the blockstore.
pub fn insert(&self, headers: &[Header]) -> Result> {
let (batch, ret) = self.insert_batch(headers)?;
self.0.apply_batch(batch)?;
Ok(ret)
}
/// Generate the sled batch corresponding to an insert, so caller
/// can handle the write operation.
/// The header's hash() function output is used as the key,
/// while value is the serialized [`Header`] itself.
/// On success, the function returns the header hashes in the same
/// order, along with the corresponding operation batch.
pub fn insert_batch(&self, headers: &[Header]) -> Result<(sled::Batch, Vec)> {
let mut ret = Vec::with_capacity(headers.len());
let mut batch = sled::Batch::default();
for header in headers {
let headerhash = header.hash()?;
batch.insert(headerhash.as_bytes(), serialize(header));
ret.push(headerhash);
}
Ok((batch, ret))
}
/// Check if the headerstore contains a given headerhash.
pub fn contains(&self, headerhash: &blake3::Hash) -> Result {
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>> {
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> {
let mut headers = vec![];
for header in self.0.iter() {
headers.push(parse_record(header.unwrap())?);
}
Ok(headers)
}
}
/// Overlay structure over a [`HeaderStore`] instance.
pub struct HeaderStoreOverlay(SledDbOverlayPtr);
impl HeaderStoreOverlay {
pub fn new(overlay: &SledDbOverlayPtr) -> Result {
overlay.lock().unwrap().open_tree(SLED_HEADER_TREE)?;
Ok(Self(overlay.clone()))
}
/// Insert a slice of [`Header`] into the overlay.
/// The header's hash() function output 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> {
let mut ret = Vec::with_capacity(headers.len());
let mut lock = self.0.lock().unwrap();
for header in headers {
let headerhash = header.hash()?;
lock.insert(SLED_HEADER_TREE, headerhash.as_bytes(), &serialize(header))?;
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>> {
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)
}
}