2025-02-12 03:41:50 -05:00
|
|
|
use alloc::vec::Vec;
|
|
|
|
|
|
|
|
|
|
use borsh::{BorshSerialize, BorshDeserialize};
|
|
|
|
|
|
2025-03-04 04:00:05 -05:00
|
|
|
use crate::{
|
|
|
|
|
primitives::{BlockHash, merkle::UnbalancedMerkleTree},
|
|
|
|
|
Transaction,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/// The tag for the hash of a transaction's event, forming a leaf of the Merkle tree of its events.
|
2025-03-04 06:00:06 -05:00
|
|
|
pub const TRANSACTION_EVENTS_COMMITMENT_LEAF_TAG: u8 = 0;
|
2025-03-04 04:00:05 -05:00
|
|
|
/// The tag for the branch hashes of transaction events.
|
2025-03-04 06:00:06 -05:00
|
|
|
pub const TRANSACTION_EVENTS_COMMITMENT_BRANCH_TAG: u8 = 1;
|
2025-03-04 04:00:05 -05:00
|
|
|
/// The tag for the hash of a transaction's hash and its events' Merkle root, forming a leaf of the
|
|
|
|
|
/// Merkle tree which is the events commitment.
|
2025-03-04 06:00:06 -05:00
|
|
|
pub const EVENTS_COMMITMENT_LEAF_TAG: u8 = 2;
|
2025-03-04 04:00:05 -05:00
|
|
|
/// The tag for for the branch hashes of the Merkle tree which is the events commitments.
|
2025-03-04 06:00:06 -05:00
|
|
|
pub const EVENTS_COMMITMENT_BRANCH_TAG: u8 = 3;
|
2025-02-12 03:41:50 -05:00
|
|
|
|
|
|
|
|
/// A V1 header for a block.
|
|
|
|
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
|
|
|
|
pub struct HeaderV1 {
|
|
|
|
|
/// The index of this block on the blockchain.
|
|
|
|
|
///
|
|
|
|
|
/// The genesis block has number 0.
|
|
|
|
|
pub number: u64,
|
2025-02-26 05:05:35 -05:00
|
|
|
/// The commitment to the DAG this header builds upon.
|
2025-03-04 06:00:06 -05:00
|
|
|
///
|
|
|
|
|
/// This is defined as an unbalanced Merkle tree so light clients may sync one header per epoch,
|
|
|
|
|
/// and then may prove the inclusion of any header in logarithmic depth (without providing the
|
|
|
|
|
/// entire header chain).
|
|
|
|
|
///
|
|
|
|
|
/// Alternative popular options would be a Merkle Mountain Range, which makes more recent blocks
|
|
|
|
|
/// cheaper to prove at the sacrifice of older blocks being more expensive to prove. An MMR isn't
|
|
|
|
|
/// used in order to minimize the protocol's surface area. Additionally, even though the
|
|
|
|
|
/// unbalanced Merkle tree doesn't achieve such notably short paths for recent blocks, it does
|
|
|
|
|
/// inherently provide lower-depth paths to more recent items *on imbalance*.
|
|
|
|
|
pub builds_upon: UnbalancedMerkleTree,
|
2025-02-17 02:14:31 -05:00
|
|
|
/// The UNIX time in milliseconds this block was created at.
|
|
|
|
|
pub unix_time_in_millis: u64,
|
2025-02-26 05:05:35 -05:00
|
|
|
/// The commitment to the transactions within this block.
|
2025-03-04 06:00:06 -05:00
|
|
|
pub transactions_commitment: UnbalancedMerkleTree,
|
2025-03-04 04:00:05 -05:00
|
|
|
/// The commitment to the events within this block.
|
2025-03-04 06:00:06 -05:00
|
|
|
///
|
|
|
|
|
/// The leaves of this tree will be of the form
|
|
|
|
|
/// `(EVENTS_COMMITMENT_LEAF_TAG, transaction hash, transaction's events' Merkle tree root)`.
|
|
|
|
|
/// A transaction may have the same event multiple times, yet an event may be uniquely identified
|
|
|
|
|
/// by its path within the tree.
|
2025-03-04 04:00:05 -05:00
|
|
|
pub events_commitment: UnbalancedMerkleTree,
|
2025-02-12 03:41:50 -05:00
|
|
|
/// A commitment to the consensus data used to justify adding this block to the blockchain.
|
|
|
|
|
pub consensus_commitment: [u8; 32],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A header for a block.
|
|
|
|
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
|
|
|
|
pub enum Header {
|
|
|
|
|
/// A version 1 header.
|
|
|
|
|
V1(HeaderV1),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Header {
|
|
|
|
|
/// Get the hash of the header.
|
|
|
|
|
pub fn number(&self) -> u64 {
|
|
|
|
|
match self {
|
|
|
|
|
Header::V1(HeaderV1 { number, .. }) => *number,
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-02-26 05:05:35 -05:00
|
|
|
/// Get the commitment to the DAG this header builds upon.
|
2025-03-04 06:00:06 -05:00
|
|
|
pub fn builds_upon(&self) -> UnbalancedMerkleTree {
|
2025-02-12 03:41:50 -05:00
|
|
|
match self {
|
2025-02-26 05:05:35 -05:00
|
|
|
Header::V1(HeaderV1 { builds_upon, .. }) => *builds_upon,
|
2025-02-12 03:41:50 -05:00
|
|
|
}
|
|
|
|
|
}
|
2025-02-26 05:05:35 -05:00
|
|
|
/// The commitment to the transactions within this block.
|
2025-03-04 06:00:06 -05:00
|
|
|
pub fn transactions_commitment(&self) -> UnbalancedMerkleTree {
|
2025-02-12 03:41:50 -05:00
|
|
|
match self {
|
2025-02-26 05:05:35 -05:00
|
|
|
Header::V1(HeaderV1 { transactions_commitment, .. }) => *transactions_commitment,
|
2025-02-12 03:41:50 -05:00
|
|
|
}
|
|
|
|
|
}
|
2025-03-04 06:00:06 -05:00
|
|
|
/// The commitment to the events within this block.
|
|
|
|
|
pub fn events_commitment(&self) -> UnbalancedMerkleTree {
|
|
|
|
|
match self {
|
|
|
|
|
Header::V1(HeaderV1 { events_commitment, .. }) => *events_commitment,
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-02-12 03:41:50 -05:00
|
|
|
/// Get the hash of the header.
|
|
|
|
|
pub fn hash(&self) -> BlockHash {
|
|
|
|
|
BlockHash(sp_core::blake2_256(&borsh::to_vec(self).unwrap()))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A block.
|
|
|
|
|
///
|
|
|
|
|
/// This does not guarantee consistency. The header's `transactions_root` may not match the
|
|
|
|
|
/// contained transactions.
|
|
|
|
|
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
|
|
|
|
pub struct Block {
|
|
|
|
|
/// The block's header.
|
|
|
|
|
pub header: Header,
|
|
|
|
|
/// The block's transactions.
|
|
|
|
|
pub transactions: Vec<Transaction>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "substrate")]
|
|
|
|
|
mod substrate {
|
2025-02-26 05:05:35 -05:00
|
|
|
use core::fmt::Debug;
|
|
|
|
|
|
2025-02-12 03:41:50 -05:00
|
|
|
use scale::{Encode, Decode};
|
|
|
|
|
use scale_info::TypeInfo;
|
|
|
|
|
|
|
|
|
|
use sp_core::H256;
|
|
|
|
|
use sp_runtime::{
|
2025-02-26 05:05:35 -05:00
|
|
|
generic::{DigestItem, Digest},
|
2025-02-12 03:41:50 -05:00
|
|
|
traits::{Header as HeaderTrait, HeaderProvider, Block as BlockTrait},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
use super::*;
|
2025-02-26 05:05:35 -05:00
|
|
|
|
2025-03-04 06:00:06 -05:00
|
|
|
/// The digest for all of the Serai-specific header fields added before execution of the block.
|
2025-02-26 05:05:35 -05:00
|
|
|
#[derive(Clone, Copy, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
|
2025-03-04 06:00:06 -05:00
|
|
|
pub struct SeraiPreExecutionDigest {
|
2025-02-26 05:05:35 -05:00
|
|
|
/// The UNIX time in milliseconds this block was created at.
|
|
|
|
|
pub unix_time_in_millis: u64,
|
2025-03-04 06:00:06 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The digest for all of the Serai-specific header fields determined during execution of the
|
|
|
|
|
/// block.
|
|
|
|
|
#[derive(Clone, Copy, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
|
|
|
|
|
pub struct SeraiExecutionDigest {
|
|
|
|
|
/// The commitment to the DAG this header builds upon.
|
|
|
|
|
pub builds_upon: UnbalancedMerkleTree,
|
2025-02-26 05:05:35 -05:00
|
|
|
/// The commitment to the transactions within this block.
|
2025-03-04 06:00:06 -05:00
|
|
|
pub transactions_commitment: UnbalancedMerkleTree,
|
2025-03-04 04:00:05 -05:00
|
|
|
/// The commitment to the events within this block.
|
|
|
|
|
pub events_commitment: UnbalancedMerkleTree,
|
2025-02-26 05:05:35 -05:00
|
|
|
}
|
|
|
|
|
|
2025-03-04 06:00:06 -05:00
|
|
|
impl SeraiPreExecutionDigest {
|
|
|
|
|
/// The consensus ID for a Serai pre-execution digest.
|
|
|
|
|
pub const CONSENSUS_ID: [u8; 4] = *b"SRIP";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl SeraiExecutionDigest {
|
|
|
|
|
/// The consensus ID for a Serai execution digest.
|
|
|
|
|
pub const CONSENSUS_ID: [u8; 4] = *b"SRIE";
|
2025-02-26 05:05:35 -05:00
|
|
|
}
|
2025-02-12 03:41:50 -05:00
|
|
|
|
|
|
|
|
/// The consensus data for a V1 header.
|
|
|
|
|
///
|
|
|
|
|
/// This is not considered part of the protocol proper and may be pruned in the future. It's
|
|
|
|
|
/// solely considered used for consensus now.
|
|
|
|
|
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, sp_runtime::Serialize)]
|
|
|
|
|
pub struct ConsensusV1 {
|
2025-02-26 05:05:35 -05:00
|
|
|
/// The hash of the immediately preceding block.
|
|
|
|
|
parent_hash: H256,
|
|
|
|
|
/// The root for the Merkle tree of transactions, as defined by Substrate.
|
|
|
|
|
///
|
|
|
|
|
/// The format of this differs from Serai's format for the commitment to the transactions.
|
|
|
|
|
transactions_root: H256,
|
2025-02-12 03:41:50 -05:00
|
|
|
/// The state root.
|
|
|
|
|
state_root: H256,
|
|
|
|
|
/// The consensus digests.
|
|
|
|
|
digest: Digest,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A V1 header for a block, as needed by Substrate.
|
|
|
|
|
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, sp_runtime::Serialize)]
|
|
|
|
|
pub struct SubstrateHeaderV1 {
|
|
|
|
|
number: u64,
|
|
|
|
|
consensus: ConsensusV1,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A header for a block, as needed by Substrate.
|
|
|
|
|
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, sp_runtime::Serialize)]
|
|
|
|
|
pub enum SubstrateHeader {
|
|
|
|
|
/// A version 1 header.
|
|
|
|
|
V1(SubstrateHeaderV1),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<&SubstrateHeader> for Header {
|
2025-02-17 02:14:31 -05:00
|
|
|
fn from(header: &SubstrateHeader) -> Self {
|
2025-02-12 03:41:50 -05:00
|
|
|
match header {
|
2025-02-26 05:05:35 -05:00
|
|
|
SubstrateHeader::V1(header) => {
|
2025-03-04 06:00:06 -05:00
|
|
|
let mut pre_execution_digest = None;
|
|
|
|
|
let mut execution_digest = None;
|
|
|
|
|
for log in header.consensus.digest.logs() {
|
|
|
|
|
match log {
|
2025-02-26 05:05:35 -05:00
|
|
|
DigestItem::PreRuntime(consensus, encoded)
|
2025-03-04 06:00:06 -05:00
|
|
|
if *consensus == SeraiExecutionDigest::CONSENSUS_ID =>
|
2025-02-26 05:05:35 -05:00
|
|
|
{
|
2025-03-04 06:00:06 -05:00
|
|
|
pre_execution_digest =
|
|
|
|
|
SeraiPreExecutionDigest::deserialize_reader(&mut encoded.as_slice()).ok();
|
2025-02-26 05:05:35 -05:00
|
|
|
}
|
2025-03-04 06:00:06 -05:00
|
|
|
DigestItem::Consensus(consensus, encoded)
|
|
|
|
|
if *consensus == SeraiExecutionDigest::CONSENSUS_ID =>
|
|
|
|
|
{
|
|
|
|
|
execution_digest =
|
|
|
|
|
SeraiExecutionDigest::deserialize_reader(&mut encoded.as_slice()).ok();
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-02-26 05:05:35 -05:00
|
|
|
Header::V1(HeaderV1 {
|
|
|
|
|
number: header.number,
|
2025-03-04 06:00:06 -05:00
|
|
|
builds_upon: execution_digest
|
2025-02-26 05:05:35 -05:00
|
|
|
.as_ref()
|
|
|
|
|
.map(|digest| digest.builds_upon)
|
2025-03-04 06:00:06 -05:00
|
|
|
.unwrap_or(UnbalancedMerkleTree::EMPTY),
|
|
|
|
|
unix_time_in_millis: pre_execution_digest
|
2025-02-26 05:05:35 -05:00
|
|
|
.as_ref()
|
|
|
|
|
.map(|digest| digest.unix_time_in_millis)
|
|
|
|
|
.unwrap_or(0),
|
2025-03-04 06:00:06 -05:00
|
|
|
transactions_commitment: execution_digest
|
2025-02-26 05:05:35 -05:00
|
|
|
.as_ref()
|
|
|
|
|
.map(|digest| digest.transactions_commitment)
|
2025-03-04 06:00:06 -05:00
|
|
|
.unwrap_or(UnbalancedMerkleTree::EMPTY),
|
|
|
|
|
events_commitment: execution_digest
|
2025-03-04 04:00:05 -05:00
|
|
|
.as_ref()
|
|
|
|
|
.map(|digest| digest.events_commitment)
|
|
|
|
|
.unwrap_or(UnbalancedMerkleTree::EMPTY),
|
2025-02-26 05:05:35 -05:00
|
|
|
consensus_commitment: sp_core::blake2_256(&header.consensus.encode()),
|
|
|
|
|
})
|
|
|
|
|
}
|
2025-02-12 03:41:50 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A block, as needed by Substrate.
|
2025-02-26 05:05:35 -05:00
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, sp_runtime::Serialize)]
|
2025-02-26 05:19:04 -05:00
|
|
|
pub struct SubstrateBlock {
|
2025-02-12 03:41:50 -05:00
|
|
|
header: SubstrateHeader,
|
|
|
|
|
#[serde(skip)] // This makes this unsafe to deserialize, but we don't impl `Deserialize`
|
2025-02-26 05:19:04 -05:00
|
|
|
transactions: Vec<Transaction>,
|
2025-02-12 03:41:50 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl HeaderTrait for SubstrateHeader {
|
|
|
|
|
type Number = u64;
|
|
|
|
|
type Hash = H256;
|
|
|
|
|
type Hashing = sp_runtime::traits::BlakeTwo256;
|
|
|
|
|
|
|
|
|
|
fn new(
|
|
|
|
|
number: Self::Number,
|
|
|
|
|
extrinsics_root: Self::Hash,
|
|
|
|
|
state_root: Self::Hash,
|
|
|
|
|
parent_hash: Self::Hash,
|
|
|
|
|
digest: Digest,
|
|
|
|
|
) -> Self {
|
|
|
|
|
SubstrateHeader::V1(SubstrateHeaderV1 {
|
|
|
|
|
number,
|
2025-02-26 05:05:35 -05:00
|
|
|
consensus: ConsensusV1 {
|
|
|
|
|
parent_hash,
|
|
|
|
|
transactions_root: extrinsics_root,
|
|
|
|
|
state_root,
|
|
|
|
|
digest,
|
|
|
|
|
},
|
2025-02-12 03:41:50 -05:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn number(&self) -> &Self::Number {
|
|
|
|
|
match self {
|
|
|
|
|
SubstrateHeader::V1(SubstrateHeaderV1 { number, .. }) => number,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
fn set_number(&mut self, number: Self::Number) {
|
|
|
|
|
match self {
|
|
|
|
|
SubstrateHeader::V1(SubstrateHeaderV1 { number: existing, .. }) => {
|
|
|
|
|
*existing = number;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn extrinsics_root(&self) -> &Self::Hash {
|
|
|
|
|
match self {
|
2025-02-26 05:05:35 -05:00
|
|
|
SubstrateHeader::V1(SubstrateHeaderV1 { consensus, .. }) => &consensus.transactions_root,
|
2025-02-12 03:41:50 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
fn set_extrinsics_root(&mut self, extrinsics_root: Self::Hash) {
|
|
|
|
|
match self {
|
2025-02-26 05:05:35 -05:00
|
|
|
SubstrateHeader::V1(SubstrateHeaderV1 { consensus, .. }) => {
|
|
|
|
|
consensus.transactions_root = extrinsics_root;
|
2025-02-12 03:41:50 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn state_root(&self) -> &Self::Hash {
|
|
|
|
|
match self {
|
|
|
|
|
SubstrateHeader::V1(SubstrateHeaderV1 { consensus, .. }) => &consensus.state_root,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
fn set_state_root(&mut self, state_root: Self::Hash) {
|
|
|
|
|
match self {
|
|
|
|
|
SubstrateHeader::V1(SubstrateHeaderV1 { consensus, .. }) => {
|
|
|
|
|
consensus.state_root = state_root;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn parent_hash(&self) -> &Self::Hash {
|
|
|
|
|
match self {
|
2025-02-26 05:05:35 -05:00
|
|
|
SubstrateHeader::V1(SubstrateHeaderV1 { consensus, .. }) => &consensus.parent_hash,
|
2025-02-12 03:41:50 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
fn set_parent_hash(&mut self, parent_hash: Self::Hash) {
|
|
|
|
|
match self {
|
2025-02-26 05:05:35 -05:00
|
|
|
SubstrateHeader::V1(SubstrateHeaderV1 { consensus, .. }) => {
|
|
|
|
|
consensus.parent_hash = parent_hash;
|
2025-02-12 03:41:50 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn digest(&self) -> &Digest {
|
|
|
|
|
match self {
|
|
|
|
|
SubstrateHeader::V1(SubstrateHeaderV1 { consensus, .. }) => &consensus.digest,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
fn digest_mut(&mut self) -> &mut Digest {
|
|
|
|
|
match self {
|
|
|
|
|
SubstrateHeader::V1(SubstrateHeaderV1 { consensus, .. }) => &mut consensus.digest,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn hash(&self) -> H256 {
|
|
|
|
|
H256::from(Header::from(self).hash().0)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-26 05:19:04 -05:00
|
|
|
impl HeaderProvider for SubstrateBlock {
|
2025-02-12 03:41:50 -05:00
|
|
|
type HeaderT = SubstrateHeader;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-26 05:19:04 -05:00
|
|
|
impl BlockTrait for SubstrateBlock {
|
|
|
|
|
type Extrinsic = Transaction;
|
2025-02-12 03:41:50 -05:00
|
|
|
type Header = SubstrateHeader;
|
|
|
|
|
type Hash = H256;
|
|
|
|
|
fn header(&self) -> &Self::Header {
|
|
|
|
|
&self.header
|
|
|
|
|
}
|
|
|
|
|
fn extrinsics(&self) -> &[Self::Extrinsic] {
|
|
|
|
|
&self.transactions
|
|
|
|
|
}
|
|
|
|
|
fn deconstruct(self) -> (Self::Header, Vec<Self::Extrinsic>) {
|
|
|
|
|
(self.header, self.transactions)
|
|
|
|
|
}
|
|
|
|
|
fn new(header: Self::Header, transactions: Vec<Self::Extrinsic>) -> Self {
|
|
|
|
|
Self { header, transactions }
|
|
|
|
|
}
|
|
|
|
|
fn encode_from(header: &Self::Header, transactions: &[Self::Extrinsic]) -> Vec<u8> {
|
|
|
|
|
let header = header.encode();
|
|
|
|
|
let transactions = transactions.encode();
|
|
|
|
|
let mut block = header;
|
|
|
|
|
block.extend(transactions);
|
|
|
|
|
block
|
|
|
|
|
}
|
|
|
|
|
fn hash(&self) -> Self::Hash {
|
|
|
|
|
self.header.hash()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#[cfg(feature = "substrate")]
|
|
|
|
|
pub use substrate::*;
|