/* 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::Slot,
crypto::schnorr::Signature,
pasta::{group::ff::Field, pallas},
};
use darkfi_serial::{async_trait, deserialize, serialize, SerialDecodable, SerialEncodable};
use crate::{tx::Transaction, Error, Result};
use super::{parse_record, parse_u64_key_record, validate_slot, Header, SledDbOverlayPtr};
/// Block version number
pub const BLOCK_VERSION: u8 = 1;
/// Block magic bytes
const BLOCK_MAGIC_BYTES: [u8; 4] = [0x11, 0x6d, 0x75, 0x1f];
/// This struct represents a tuple of the form (`magic`, `header`, `txs`, `producer`, `slots`).
/// The header and transactions are stored as hashes, while slots are stored as integers,
/// serving as pointers to the actual data in the sled database.
#[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
pub struct Block {
/// Block magic bytes
pub magic: [u8; 4],
/// Block header
pub header: blake3::Hash,
/// Trasaction hashes
pub txs: Vec,
/// Block producer info
pub producer: BlockProducer,
/// Slots up until this block
pub slots: Vec,
}
impl Block {
pub fn new(
header: blake3::Hash,
txs: Vec,
producer: BlockProducer,
slots: Vec,
) -> Self {
let magic = BLOCK_MAGIC_BYTES;
Self { magic, header, txs, producer, slots }
}
/// Calculate the block hash
pub fn blockhash(&self) -> blake3::Hash {
blake3::hash(&serialize(self))
}
}
/// Structure representing full block data.
#[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
pub struct BlockInfo {
/// Block magic bytes
pub magic: [u8; 4],
/// Block header data
pub header: Header,
/// Transactions payload
pub txs: Vec,
/// Block producer info
pub producer: BlockProducer,
/// Slots payload
pub slots: Vec,
}
impl Default for BlockInfo {
/// Represents the genesis block on current timestamp
fn default() -> Self {
let magic = BLOCK_MAGIC_BYTES;
Self {
magic,
header: Header::default(),
txs: vec![],
producer: BlockProducer::default(),
slots: vec![Slot::default()],
}
}
}
impl BlockInfo {
pub fn new(
header: Header,
txs: Vec,
producer: BlockProducer,
slots: Vec,
) -> Self {
let magic = BLOCK_MAGIC_BYTES;
Self { magic, header, txs, producer, slots }
}
/// Calculate the block hash
pub fn blockhash(&self) -> blake3::Hash {
let block: Block = self.clone().into();
block.blockhash()
}
/// A block is considered valid when the following rules apply:
/// 1. Parent hash is equal to the hash of the previous block
/// 2. Timestamp increments previous block timestamp
/// 3. Slot increments previous block slot
/// 4. Slots vector is not empty and all its slots are valid
/// 5. Slot is the same as the slots vector last slot id
/// Additional validity rules can be applied.
pub fn validate(&self, previous: &Self, expected_reward: u64) -> Result<()> {
let error = Err(Error::BlockIsInvalid(self.blockhash().to_string()));
let previous_hash = previous.blockhash();
// Check previous hash (1)
if self.header.previous != previous_hash {
return error
}
// Check timestamps are incremental (2)
if self.header.timestamp <= previous.header.timestamp {
return error
}
// Check slots are incremental (3)
if self.header.slot <= previous.header.slot {
return error
}
// Verify slots (4)
if self.slots.is_empty() {
return error
}
// Retrieve previous block last slot
let mut previous_slot = previous.slots.last().unwrap();
// Check if empty slots existed
if self.slots.len() > 1 {
// All slots exluding the last one must have reward value set to 0.
// Slots must already be in correct order (sorted by id).
for slot in &self.slots[..self.slots.len() - 1] {
validate_slot(
slot,
previous_slot,
&previous_hash,
&previous.header.previous,
&previous.producer.eta,
0,
)?;
previous_slot = slot;
}
}
validate_slot(
self.slots.last().unwrap(),
previous_slot,
&previous_hash,
&previous.header.previous,
&previous.producer.eta,
expected_reward,
)?;
// Check block slot is the last slot id (5)
if self.slots.last().unwrap().id != self.header.slot {
return error
}
Ok(())
}
}
impl From for Block {
fn from(block_info: BlockInfo) -> Self {
let txs = block_info.txs.iter().map(|x| blake3::hash(&serialize(x))).collect();
let slots = block_info.slots.iter().map(|x| x.id).collect();
Self {
magic: block_info.magic,
header: block_info.header.headerhash(),
txs,
producer: block_info.producer,
slots,
}
}
}
/// [`Block`] sled tree
const SLED_BLOCK_TREE: &[u8] = b"_blocks";
/// 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)]
pub struct BlockStore(pub sled::Tree);
impl BlockStore {
/// Opens a new or existing `BlockStore` on the given sled database.
pub fn new(db: &sled::Db) -> Result {
let tree = db.open_tree(SLED_BLOCK_TREE)?;
Ok(Self(tree))
}
/// Insert a slice of [`Block`] into the store.
pub fn insert(&self, blocks: &[Block]) -> Result> {
let (batch, ret) = self.insert_batch(blocks)?;
self.0.apply_batch(batch)?;
Ok(ret)
}
/// Generate the sled batch corresponding to an insert, so caller
/// can handle the write operation.
/// The blocks are hashed with BLAKE3 and this block hash 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_batch(&self, blocks: &[Block]) -> Result<(sled::Batch, Vec)> {
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);
}
Ok((batch, ret))
}
/// Check if the block store contains a given block hash.
pub fn contains(&self, blockhash: &blake3::Hash) -> Result {
Ok(self.0.contains_key(blockhash.as_bytes())?)
}
/// Fetch given block hashes from the block store.
/// The resulting vector contains `Option`, which is `Some` if the block
/// was found in the block store, 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>> {
let mut ret = Vec::with_capacity(block_hashes.len());
for hash in block_hashes {
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 block store in the form of a tuple
/// (`hash`, `block`).
/// Be careful as this will try to load everything in memory.
pub fn get_all(&self) -> Result> {
let mut blocks = vec![];
for block in self.0.iter() {
blocks.push(parse_record(block.unwrap())?);
}
Ok(blocks)
}
}
/// Overlay structure over a [`BlockStore`] instance.
pub struct BlockStoreOverlay(SledDbOverlayPtr);
impl BlockStoreOverlay {
pub fn new(overlay: &SledDbOverlayPtr) -> Result {
overlay.lock().unwrap().open_tree(SLED_BLOCK_TREE)?;
Ok(Self(overlay.clone()))
}
/// Insert a slice of [`Block`] into the overlay.
/// The block are hashed with BLAKE3 and this block hash 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> {
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>> {
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 {
/// Order number
pub number: u64,
/// Block headerhash of that number
pub block: blake3::Hash,
}
/// [`BlockOrder`] sled tree
const SLED_BLOCK_ORDER_TREE: &[u8] = b"_block_order";
/// The `BlockOrderStore` is a `sled` tree storing the order of the
/// blockchain's blocks, where the key is the order number, and the value is
/// the blocks' hash. [`BlockStore`] can be queried with this hash.
#[derive(Clone)]
pub struct BlockOrderStore(pub sled::Tree);
impl BlockOrderStore {
/// Opens a new or existing `BlockOrderStore` on the given sled database.
pub fn new(db: &sled::Db) -> Result {
let tree = db.open_tree(SLED_BLOCK_ORDER_TREE)?;
Ok(Self(tree))
}
/// Insert a slice of `u64` and block hashes into the store.
pub fn insert(&self, order: &[u64], hashes: &[blake3::Hash]) -> Result<()> {
let batch = self.insert_batch(order, hashes)?;
self.0.apply_batch(batch)?;
Ok(())
}
/// Generate the sled batch corresponding to an insert, so caller
/// can handle the write operation.
/// The block order number is used as the key, and the block hash is used as value.
pub fn insert_batch(&self, order: &[u64], hashes: &[blake3::Hash]) -> Result {
if order.len() != hashes.len() {
return Err(Error::InvalidInputLengths)
}
let mut batch = sled::Batch::default();
for (i, number) in order.iter().enumerate() {
batch.insert(&number.to_be_bytes(), hashes[i].as_bytes());
}
Ok(batch)
}
/// Check if the block order store contains a given order number.
pub fn contains(&self, number: u64) -> Result {
Ok(self.0.contains_key(number.to_be_bytes())?)
}
/// Fetch given order numbers from the block order store.
/// The resulting vector contains `Option`, which is `Some` if the number
/// was found in the block order store, 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 order number was not found.
pub fn get(&self, order: &[u64], strict: bool) -> Result>> {
let mut ret = Vec::with_capacity(order.len());
for number in order {
if let Some(found) = self.0.get(number.to_be_bytes())? {
let block_hash = deserialize(&found)?;
ret.push(Some(block_hash));
} else {
if strict {
return Err(Error::BlockNumberNotFound(*number))
}
ret.push(None);
}
}
Ok(ret)
}
/// Retrieve complete order from the block order store in the form of
/// a vector containing (`number`, `hash`) tuples.
/// Be careful as this will try to load everything in memory.
pub fn get_all(&self) -> Result> {
let mut order = vec![];
for record in self.0.iter() {
order.push(parse_u64_key_record(record.unwrap())?);
}
Ok(order)
}
/// Fetch n hashes after given order number. In the iteration, if an order
/// number is not found, the iteration stops and the function returns what
/// it has found so far in the `BlockOrderStore`.
pub fn get_after(&self, number: u64, n: u64) -> Result> {
let mut ret = vec![];
let mut key = number;
let mut counter = 0;
while counter <= n {
if let Some(found) = self.0.get_gt(key.to_be_bytes())? {
let (number, hash) = parse_u64_key_record(found)?;
key = number;
ret.push(hash);
counter += 1;
continue
}
break
}
Ok(ret)
}
/// Fetch the first block hash in the tree, based on the `Ord`
/// implementation for `Vec`.
pub fn get_first(&self) -> Result<(u64, blake3::Hash)> {
let found = match self.0.first()? {
Some(s) => s,
None => return Err(Error::BlockNumberNotFound(0)),
};
let (number, hash) = parse_u64_key_record(found)?;
Ok((number, hash))
}
/// Fetch the last block hash in the tree, based on the `Ord`
/// implementation for `Vec`.
pub fn get_last(&self) -> Result<(u64, blake3::Hash)> {
let found = self.0.last()?.unwrap();
let (number, hash) = parse_u64_key_record(found)?;
Ok((number, 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.is_empty()
}
}
/// Overlay structure over a [`BlockOrderStore`] instance.
pub struct BlockOrderStoreOverlay(SledDbOverlayPtr);
impl BlockOrderStoreOverlay {
pub fn new(overlay: &SledDbOverlayPtr) -> Result {
overlay.lock().unwrap().open_tree(SLED_BLOCK_ORDER_TREE)?;
Ok(Self(overlay.clone()))
}
/// Insert a slice of `u64` and block hashes into the store. With sled, the
/// operation is done as a batch.
/// The block order number is used as the key, and the blockhash is used as value.
pub fn insert(&self, order: &[u64], hashes: &[blake3::Hash]) -> Result<()> {
if order.len() != hashes.len() {
return Err(Error::InvalidInputLengths)
}
let mut lock = self.0.lock().unwrap();
for (i, number) in order.iter().enumerate() {
lock.insert(SLED_BLOCK_ORDER_TREE, &number.to_be_bytes(), hashes[i].as_bytes())?;
}
Ok(())
}
/// Fetch given order numbers from the overlay.
/// The resulting vector contains `Option`, which is `Some` if the number
/// 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 number was not found.
pub fn get(&self, order: &[u64], strict: bool) -> Result>> {
let mut ret = Vec::with_capacity(order.len());
let lock = self.0.lock().unwrap();
for number in order {
if let Some(found) = lock.get(SLED_BLOCK_ORDER_TREE, &number.to_be_bytes())? {
let block_hash = deserialize(&found)?;
ret.push(Some(block_hash));
} else {
if strict {
return Err(Error::BlockNumberNotFound(*number))
}
ret.push(None);
}
}
Ok(ret)
}
/// Fetch the last block hash in the overlay, based on the `Ord`
/// implementation for `Vec`.
pub fn get_last(&self) -> Result<(u64, blake3::Hash)> {
let found = self.0.lock().unwrap().last(SLED_BLOCK_ORDER_TREE)?.unwrap();
let (number, hash) = parse_u64_key_record(found)?;
Ok((number, hash))
}
/// Check if overlay contains any records
pub fn is_empty(&self) -> Result {
Ok(self.0.lock().unwrap().is_empty(SLED_BLOCK_ORDER_TREE)?)
}
}
/// This struct represents [`Block`] producer information.
#[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
pub struct BlockProducer {
/// Block producer signature
pub signature: Signature,
/// Proposal transaction
pub proposal: Transaction,
/// Block producer ETA
pub eta: pallas::Base,
}
impl BlockProducer {
pub fn new(signature: Signature, proposal: Transaction, eta: pallas::Base) -> Self {
Self { signature, proposal, eta }
}
}
impl Default for BlockProducer {
fn default() -> Self {
let signature = Signature::dummy();
let proposal = Transaction::default();
let eta = pallas::Base::ZERO;
Self { signature, proposal, eta }
}
}