/* 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 std::fmt;
use darkfi_sdk::{
crypto::{MerkleNode, MerkleTree},
pasta::pallas,
};
use darkfi_serial::{async_trait, serialize, SerialDecodable, SerialEncodable};
use super::{
constants::{BLOCK_MAGIC_BYTES, BLOCK_VERSION},
LeadInfo,
};
use crate::{impl_p2p_message, net::Message, tx::Transaction, util::time::Timestamp};
/// This struct represents a tuple of the form (version, previous, epoch, slot, timestamp, 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
pub epoch: u64,
/// Slot UID
pub slot: u64,
/// Block creation timestamp
pub timestamp: Timestamp,
/// Root of the transaction hashes merkle tree
pub root: MerkleNode,
}
impl Header {
pub fn new(
previous: blake3::Hash,
epoch: u64,
slot: u64,
timestamp: Timestamp,
root: MerkleNode,
) -> Self {
let version = BLOCK_VERSION;
Self { version, previous, epoch, slot, timestamp, root }
}
/// Generate the genesis block.
pub fn genesis_header(genesis_ts: Timestamp, genesis_data: blake3::Hash) -> Self {
let tree = MerkleTree::new(100);
let root = tree.root(0).unwrap();
Self::new(genesis_data, 0, 0, genesis_ts, root)
}
/// Calculate the header hash
pub fn headerhash(&self) -> blake3::Hash {
blake3::hash(&serialize(self))
}
}
impl Default for Header {
fn default() -> Self {
Header::new(
blake3::hash(b""),
0,
0,
Timestamp::current_time(),
MerkleNode::from(pallas::Base::zero()),
)
}
}
/// This struct represents a tuple of the form (`magic`, `header`, `counter`, `txs`, `lead_info`).
/// The header and transactions are stored as hashes, 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,
/// Lead Info
pub lead_info: LeadInfo,
}
impl_p2p_message!(Block, "block");
impl Block {
pub fn new(
previous: blake3::Hash,
epoch: u64,
slot: u64,
txs: Vec,
root: MerkleNode,
lead_info: LeadInfo,
) -> Self {
let magic = BLOCK_MAGIC_BYTES;
let timestamp = Timestamp::current_time();
let header = Header::new(previous, epoch, slot, timestamp, root);
let header = header.headerhash();
Self { magic, header, txs, lead_info }
}
/// Generate the genesis block.
pub fn genesis_block(genesis_ts: Timestamp, genesis_data: blake3::Hash) -> Self {
let magic = BLOCK_MAGIC_BYTES;
let header = Header::genesis_header(genesis_ts, genesis_data);
let header = header.headerhash();
let lead_info = LeadInfo::default();
Self { magic, header, txs: vec![], lead_info }
}
/// Calculate the block hash
pub fn blockhash(&self) -> blake3::Hash {
blake3::hash(&serialize(self))
}
}
/// Auxiliary structure used for blockchain syncing.
#[derive(Debug, SerialEncodable, SerialDecodable)]
pub struct BlockOrder {
/// Slot UID
pub slot: u64,
/// Block headerhash of that slot
pub block: blake3::Hash,
}
impl_p2p_message!(BlockOrder, "blockorder");
/// Structure representing full block data.
#[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
pub struct BlockInfo {
/// BlockInfo magic bytes
pub magic: [u8; 4],
/// Block header data
pub header: Header,
/// Transactions payload
pub txs: Vec,
/// Lead Info,
pub lead_info: LeadInfo,
}
impl Default for BlockInfo {
fn default() -> Self {
let magic = BLOCK_MAGIC_BYTES;
Self { magic, header: Header::default(), txs: vec![], lead_info: LeadInfo::default() }
}
}
impl_p2p_message!(BlockInfo, "blockinfo");
impl BlockInfo {
pub fn new(header: Header, txs: Vec, lead_info: LeadInfo) -> Self {
let magic = BLOCK_MAGIC_BYTES;
Self { magic, header, txs, lead_info }
}
/// Calculate the block hash
pub fn blockhash(&self) -> blake3::Hash {
let block: Block = self.clone().into();
block.blockhash()
}
}
impl From for Block {
fn from(block_info: BlockInfo) -> Self {
let txs = block_info.txs.iter().map(|x| blake3::hash(&serialize(x))).collect();
Self {
magic: block_info.magic,
header: block_info.header.headerhash(),
txs,
lead_info: block_info.lead_info,
}
}
}
/// Auxiliary structure used for blockchain syncing
#[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
pub struct BlockResponse {
/// Response blocks.
pub blocks: Vec,
}
impl_p2p_message!(BlockResponse, "blockresponse");
/// This struct represents a block proposal, used for consensus.
#[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
pub struct BlockProposal {
/// Block hash
pub hash: blake3::Hash,
/// Block header hash
pub header: blake3::Hash,
/// Block data
pub block: BlockInfo,
}
impl BlockProposal {
#[allow(clippy::too_many_arguments)]
pub fn new(header: Header, txs: Vec, lead_info: LeadInfo) -> Self {
let block = BlockInfo::new(header, txs, lead_info);
let hash = block.blockhash();
let header = block.header.headerhash();
Self { hash, header, block }
}
}
impl PartialEq for BlockProposal {
fn eq(&self, other: &Self) -> bool {
self.hash == other.hash &&
self.header == other.header &&
self.block.header == other.block.header &&
self.block.txs == other.block.txs
}
}
impl fmt::Display for BlockProposal {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_fmt(format_args!(
"BlockProposal {{ leader public key: {}, hash: {}, header: {}, epoch: {}, slot: {}, txs: {} }}",
self.block.lead_info.public_key,
self.hash,
self.header,
self.block.header.epoch,
self.block.header.slot,
self.block.txs.len()
))
}
}
impl_p2p_message!(BlockProposal, "proposal");
impl From for BlockInfo {
fn from(block: BlockProposal) -> BlockInfo {
block.block
}
}