#![forbid(unsafe_code)]
#![allow(clippy::too_many_arguments)]
#![cfg_attr(test, allow(clippy::single_element_loop))]
pub mod header;
pub use header::*;
mod helpers;
pub use helpers::*;
pub mod ratifications;
pub use ratifications::*;
pub mod ratify;
pub use ratify::*;
pub mod solutions;
pub use solutions::*;
pub mod transaction;
pub use transaction::*;
pub mod transactions;
pub use transactions::*;
pub mod transition;
pub use transition::*;
mod bytes;
mod genesis;
mod serialize;
mod string;
mod verify;
use console::{
account::PrivateKey,
network::prelude::*,
program::{Ciphertext, Record},
types::{Field, Group, U64},
};
use ledger_authority::Authority;
use ledger_committee::Committee;
use ledger_narwhal_data::Data;
use ledger_narwhal_subdag::Subdag;
use ledger_narwhal_transmission_id::TransmissionID;
use ledger_puzzle::{PuzzleSolutions, Solution, SolutionID};
#[derive(Clone, PartialEq, Eq)]
pub struct Block<N: Network> {
block_hash: N::BlockHash,
previous_hash: N::BlockHash,
header: Header<N>,
authority: Authority<N>,
ratifications: Ratifications<N>,
solutions: Solutions<N>,
aborted_solution_ids: Vec<SolutionID<N>>,
transactions: Transactions<N>,
aborted_transaction_ids: Vec<N::TransactionID>,
}
impl<N: Network> Block<N> {
pub fn new_beacon<R: Rng + CryptoRng>(
private_key: &PrivateKey<N>,
previous_hash: N::BlockHash,
header: Header<N>,
ratifications: Ratifications<N>,
solutions: Solutions<N>,
aborted_solution_ids: Vec<SolutionID<N>>,
transactions: Transactions<N>,
aborted_transaction_ids: Vec<N::TransactionID>,
rng: &mut R,
) -> Result<Self> {
let block_hash = N::hash_bhp1024(&to_bits_le![previous_hash, header.to_root()?])?;
let authority = Authority::new_beacon(private_key, block_hash, rng)?;
Self::from(
previous_hash,
header,
authority,
ratifications,
solutions,
aborted_solution_ids,
transactions,
aborted_transaction_ids,
)
}
pub fn new_quorum(
previous_hash: N::BlockHash,
header: Header<N>,
subdag: Subdag<N>,
ratifications: Ratifications<N>,
solutions: Solutions<N>,
aborted_solution_ids: Vec<SolutionID<N>>,
transactions: Transactions<N>,
aborted_transaction_ids: Vec<N::TransactionID>,
) -> Result<Self> {
let authority = Authority::new_quorum(subdag);
Self::from(
previous_hash,
header,
authority,
ratifications,
solutions,
aborted_solution_ids,
transactions,
aborted_transaction_ids,
)
}
pub fn from(
previous_hash: N::BlockHash,
header: Header<N>,
authority: Authority<N>,
ratifications: Ratifications<N>,
solutions: Solutions<N>,
aborted_solution_ids: Vec<SolutionID<N>>,
transactions: Transactions<N>,
aborted_transaction_ids: Vec<N::TransactionID>,
) -> Result<Self> {
if aborted_solution_ids.len() > Solutions::<N>::MAX_ABORTED_SOLUTIONS {
bail!(
"Cannot initialize a block with more than {} aborted solutions IDs",
Solutions::<N>::MAX_ABORTED_SOLUTIONS
);
}
if transactions.len() > Transactions::<N>::MAX_TRANSACTIONS {
bail!(
"Cannot initialize a block with more than {} confirmed transactions",
Transactions::<N>::MAX_TRANSACTIONS
);
}
if aborted_transaction_ids.len() > Transactions::<N>::MAX_ABORTED_TRANSACTIONS {
bail!(
"Cannot initialize a block with more than {} aborted transaction IDs",
Transactions::<N>::MAX_ABORTED_TRANSACTIONS
);
}
let block_hash = N::hash_bhp1024(&to_bits_le![previous_hash, header.to_root()?])?;
match &authority {
Authority::Beacon(signature) => {
let address = signature.to_address();
ensure!(signature.verify(&address, &[block_hash]), "Invalid signature for block {}", header.height());
}
Authority::Quorum(subdag) => {
Self::check_subdag_transmissions(
subdag,
&solutions,
&aborted_solution_ids,
&transactions,
&aborted_transaction_ids,
)?;
}
}
if header.solutions_root() != solutions.to_solutions_root()? {
bail!("The solutions root in the block does not correspond to the solutions");
}
let subdag_root = match &authority {
Authority::Beacon(_) => Field::<N>::zero(),
Authority::Quorum(subdag) => subdag.to_subdag_root()?,
};
if header.subdag_root() != subdag_root {
bail!("The subdag root in the block does not correspond to the authority");
}
Self::from_unchecked(
block_hash.into(),
previous_hash,
header,
authority,
ratifications,
solutions,
aborted_solution_ids,
transactions,
aborted_transaction_ids,
)
}
pub fn from_unchecked(
block_hash: N::BlockHash,
previous_hash: N::BlockHash,
header: Header<N>,
authority: Authority<N>,
ratifications: Ratifications<N>,
solutions: Solutions<N>,
aborted_solution_ids: Vec<SolutionID<N>>,
transactions: Transactions<N>,
aborted_transaction_ids: Vec<N::TransactionID>,
) -> Result<Self> {
Ok(Self {
block_hash,
previous_hash,
header,
authority,
ratifications,
solutions,
aborted_solution_ids,
transactions,
aborted_transaction_ids,
})
}
}
impl<N: Network> Block<N> {
pub const fn hash(&self) -> N::BlockHash {
self.block_hash
}
pub const fn previous_hash(&self) -> N::BlockHash {
self.previous_hash
}
pub const fn authority(&self) -> &Authority<N> {
&self.authority
}
pub const fn ratifications(&self) -> &Ratifications<N> {
&self.ratifications
}
pub const fn solutions(&self) -> &Solutions<N> {
&self.solutions
}
pub const fn aborted_solution_ids(&self) -> &Vec<SolutionID<N>> {
&self.aborted_solution_ids
}
pub const fn transactions(&self) -> &Transactions<N> {
&self.transactions
}
pub const fn aborted_transaction_ids(&self) -> &Vec<N::TransactionID> {
&self.aborted_transaction_ids
}
}
impl<N: Network> Block<N> {
pub const fn header(&self) -> &Header<N> {
&self.header
}
pub const fn previous_state_root(&self) -> N::StateRoot {
self.header.previous_state_root()
}
pub const fn transactions_root(&self) -> Field<N> {
self.header.transactions_root()
}
pub const fn finalize_root(&self) -> Field<N> {
self.header.finalize_root()
}
pub const fn ratifications_root(&self) -> Field<N> {
self.header.ratifications_root()
}
pub const fn solutions_root(&self) -> Field<N> {
self.header.solutions_root()
}
pub const fn metadata(&self) -> &Metadata<N> {
self.header.metadata()
}
pub const fn network(&self) -> u16 {
self.header.network()
}
pub const fn height(&self) -> u32 {
self.header.height()
}
pub const fn round(&self) -> u64 {
self.header.round()
}
pub const fn epoch_number(&self) -> u32 {
self.height() / N::NUM_BLOCKS_PER_EPOCH
}
pub const fn cumulative_weight(&self) -> u128 {
self.header.cumulative_weight()
}
pub const fn cumulative_proof_target(&self) -> u128 {
self.header.cumulative_proof_target()
}
pub const fn coinbase_target(&self) -> u64 {
self.header.coinbase_target()
}
pub const fn proof_target(&self) -> u64 {
self.header.proof_target()
}
pub const fn last_coinbase_target(&self) -> u64 {
self.header.last_coinbase_target()
}
pub const fn last_coinbase_timestamp(&self) -> i64 {
self.header.last_coinbase_timestamp()
}
pub const fn timestamp(&self) -> i64 {
self.header.timestamp()
}
}
impl<N: Network> Block<N> {
pub fn contains_transition(&self, transition_id: &N::TransitionID) -> bool {
self.transactions.contains_transition(transition_id)
}
pub fn contains_serial_number(&self, serial_number: &Field<N>) -> bool {
self.transactions.contains_serial_number(serial_number)
}
pub fn contains_commitment(&self, commitment: &Field<N>) -> bool {
self.transactions.contains_commitment(commitment)
}
}
impl<N: Network> Block<N> {
pub fn get_solution(&self, solution_id: &SolutionID<N>) -> Option<&Solution<N>> {
self.solutions.as_ref().and_then(|solution| solution.get_solution(solution_id))
}
pub fn get_transaction(&self, transaction_id: &N::TransactionID) -> Option<&Transaction<N>> {
self.transactions.get(transaction_id).map(|t| t.deref())
}
pub fn get_confirmed_transaction(&self, transaction_id: &N::TransactionID) -> Option<&ConfirmedTransaction<N>> {
self.transactions.get(transaction_id)
}
}
impl<N: Network> Block<N> {
pub fn find_transaction_for_transition_id(&self, transition_id: &N::TransitionID) -> Option<&Transaction<N>> {
self.transactions.find_transaction_for_transition_id(transition_id)
}
pub fn find_transaction_for_serial_number(&self, serial_number: &Field<N>) -> Option<&Transaction<N>> {
self.transactions.find_transaction_for_serial_number(serial_number)
}
pub fn find_transaction_for_commitment(&self, commitment: &Field<N>) -> Option<&Transaction<N>> {
self.transactions.find_transaction_for_commitment(commitment)
}
pub fn find_transition(&self, transition_id: &N::TransitionID) -> Option<&Transition<N>> {
self.transactions.find_transition(transition_id)
}
pub fn find_transition_for_serial_number(&self, serial_number: &Field<N>) -> Option<&Transition<N>> {
self.transactions.find_transition_for_serial_number(serial_number)
}
pub fn find_transition_for_commitment(&self, commitment: &Field<N>) -> Option<&Transition<N>> {
self.transactions.find_transition_for_commitment(commitment)
}
pub fn find_record(&self, commitment: &Field<N>) -> Option<&Record<N, Ciphertext<N>>> {
self.transactions.find_record(commitment)
}
}
impl<N: Network> Block<N> {
pub fn solution_ids(&self) -> Option<impl '_ + Iterator<Item = &SolutionID<N>>> {
self.solutions.as_ref().map(|solution| solution.solution_ids())
}
pub fn transaction_ids(&self) -> impl '_ + Iterator<Item = &N::TransactionID> {
self.transactions.transaction_ids()
}
pub fn deployments(&self) -> impl '_ + Iterator<Item = &ConfirmedTransaction<N>> {
self.transactions.deployments()
}
pub fn executions(&self) -> impl '_ + Iterator<Item = &ConfirmedTransaction<N>> {
self.transactions.executions()
}
pub fn transitions(&self) -> impl '_ + Iterator<Item = &Transition<N>> {
self.transactions.transitions()
}
pub fn transition_ids(&self) -> impl '_ + Iterator<Item = &N::TransitionID> {
self.transactions.transition_ids()
}
pub fn transition_public_keys(&self) -> impl '_ + Iterator<Item = &Group<N>> {
self.transactions.transition_public_keys()
}
pub fn transition_commitments(&self) -> impl '_ + Iterator<Item = &Field<N>> {
self.transactions.transition_commitments()
}
pub fn tags(&self) -> impl '_ + Iterator<Item = &Field<N>> {
self.transactions.tags()
}
pub fn input_ids(&self) -> impl '_ + Iterator<Item = &Field<N>> {
self.transactions.input_ids()
}
pub fn serial_numbers(&self) -> impl '_ + Iterator<Item = &Field<N>> {
self.transactions.serial_numbers()
}
pub fn output_ids(&self) -> impl '_ + Iterator<Item = &Field<N>> {
self.transactions.output_ids()
}
pub fn commitments(&self) -> impl '_ + Iterator<Item = &Field<N>> {
self.transactions.commitments()
}
pub fn records(&self) -> impl '_ + Iterator<Item = (&Field<N>, &Record<N, Ciphertext<N>>)> {
self.transactions.records()
}
pub fn nonces(&self) -> impl '_ + Iterator<Item = &Group<N>> {
self.transactions.nonces()
}
pub fn transaction_fee_amounts(&self) -> impl '_ + Iterator<Item = Result<U64<N>>> {
self.transactions.transaction_fee_amounts()
}
}
impl<N: Network> Block<N> {
pub fn into_transaction_ids(self) -> impl Iterator<Item = N::TransactionID> {
self.transactions.into_transaction_ids()
}
pub fn into_deployments(self) -> impl Iterator<Item = ConfirmedTransaction<N>> {
self.transactions.into_deployments()
}
pub fn into_executions(self) -> impl Iterator<Item = ConfirmedTransaction<N>> {
self.transactions.into_executions()
}
pub fn into_transitions(self) -> impl Iterator<Item = Transition<N>> {
self.transactions.into_transitions()
}
pub fn into_transition_ids(self) -> impl Iterator<Item = N::TransitionID> {
self.transactions.into_transition_ids()
}
pub fn into_transition_public_keys(self) -> impl Iterator<Item = Group<N>> {
self.transactions.into_transition_public_keys()
}
pub fn into_tags(self) -> impl Iterator<Item = Field<N>> {
self.transactions.into_tags()
}
pub fn into_serial_numbers(self) -> impl Iterator<Item = Field<N>> {
self.transactions.into_serial_numbers()
}
pub fn into_commitments(self) -> impl Iterator<Item = Field<N>> {
self.transactions.into_commitments()
}
pub fn into_records(self) -> impl Iterator<Item = (Field<N>, Record<N, Ciphertext<N>>)> {
self.transactions.into_records()
}
pub fn into_nonces(self) -> impl Iterator<Item = Group<N>> {
self.transactions.into_nonces()
}
}
#[cfg(test)]
pub mod test_helpers {
use super::*;
use console::account::{Address, PrivateKey};
use ledger_query::Query;
use ledger_store::{BlockStore, helpers::memory::BlockMemory};
use synthesizer_process::Process;
use once_cell::sync::OnceCell;
type CurrentNetwork = console::network::MainnetV0;
type CurrentAleo = circuit::network::AleoV0;
pub(crate) fn sample_genesis_block(rng: &mut TestRng) -> Block<CurrentNetwork> {
let (block, _, _) = sample_genesis_block_and_components(rng);
block
}
pub(crate) fn sample_genesis_block_and_transaction(
rng: &mut TestRng,
) -> (Block<CurrentNetwork>, Transaction<CurrentNetwork>) {
let (block, transaction, _) = sample_genesis_block_and_components(rng);
(block, transaction)
}
pub(crate) fn sample_genesis_block_and_components(
rng: &mut TestRng,
) -> (Block<CurrentNetwork>, Transaction<CurrentNetwork>, PrivateKey<CurrentNetwork>) {
static INSTANCE: OnceCell<(Block<CurrentNetwork>, Transaction<CurrentNetwork>, PrivateKey<CurrentNetwork>)> =
OnceCell::new();
INSTANCE.get_or_init(|| sample_genesis_block_and_components_raw(rng)).clone()
}
fn sample_genesis_block_and_components_raw(
rng: &mut TestRng,
) -> (Block<CurrentNetwork>, Transaction<CurrentNetwork>, PrivateKey<CurrentNetwork>) {
let private_key = PrivateKey::new(rng).unwrap();
let address = Address::<CurrentNetwork>::try_from(private_key).unwrap();
let locator = ("credits.aleo", "transfer_public_to_private");
let amount = 100_000_000u64;
let inputs = [address.to_string(), format!("{amount}_u64")];
let process = Process::load().unwrap();
let authorization =
process.authorize::<CurrentAleo, _>(&private_key, locator.0, locator.1, inputs.iter(), rng).unwrap();
let (_, mut trace) = process.execute::<CurrentAleo, _>(authorization, rng).unwrap();
let block_store = BlockStore::<CurrentNetwork, BlockMemory<_>>::open(None).unwrap();
trace.prepare(Query::from(block_store)).unwrap();
let execution = trace.prove_execution::<CurrentAleo, _>(locator.0, rng).unwrap();
let execution = Execution::from_str(&execution.to_string()).unwrap();
let transaction = Transaction::from_execution(execution, None).unwrap();
let confirmed = ConfirmedTransaction::accepted_execute(0, transaction.clone(), vec![]).unwrap();
let transactions = Transactions::from_iter([confirmed]);
let ratifications = Ratifications::try_from(vec![]).unwrap();
let header = Header::genesis(&ratifications, &transactions, vec![]).unwrap();
let previous_hash = <CurrentNetwork as Network>::BlockHash::default();
let block = Block::new_beacon(
&private_key,
previous_hash,
header,
ratifications,
None.into(),
vec![],
transactions,
vec![],
rng,
)
.unwrap();
assert!(block.header().is_genesis(), "Failed to initialize a genesis block");
(block, transaction, private_key)
}
pub(crate) fn sample_metadata() -> Metadata<CurrentNetwork> {
let network = CurrentNetwork::ID;
let round = u64::MAX;
let height = u32::MAX;
let cumulative_weight = u128::MAX - 1;
let cumulative_proof_target = u128::MAX - 1;
let coinbase_target = u64::MAX;
let proof_target = u64::MAX - 1;
let last_coinbase_target = u64::MAX;
let timestamp = i64::MAX - 1;
let last_coinbase_timestamp = timestamp - 1;
Metadata::new(
network,
round,
height,
cumulative_weight,
cumulative_proof_target,
coinbase_target,
proof_target,
last_coinbase_target,
last_coinbase_timestamp,
timestamp,
)
.unwrap()
}
}
#[cfg(test)]
mod tests {
use super::*;
use console::network::MainnetV0;
use indexmap::IndexMap;
type CurrentNetwork = MainnetV0;
#[test]
fn test_find_transaction_for_transition_id() {
let rng = &mut TestRng::default();
let (block, transaction) = crate::test_helpers::sample_genesis_block_and_transaction(rng);
let transactions = block.transactions();
let transitions = transaction.transitions().collect::<Vec<_>>();
assert!(!transitions.is_empty());
for transition in transitions {
assert_eq!(block.find_transaction_for_transition_id(transition.id()), Some(&transaction));
assert_eq!(transactions.find_transaction_for_transition_id(transition.id()), Some(&transaction));
}
for _ in 0..10 {
let transition_id = &rng.gen();
assert_eq!(block.find_transaction_for_transition_id(transition_id), None);
assert_eq!(transactions.find_transaction_for_transition_id(transition_id), None);
}
}
#[test]
fn test_find_transaction_for_commitment() {
let rng = &mut TestRng::default();
let (block, transaction) = crate::test_helpers::sample_genesis_block_and_transaction(rng);
let transactions = block.transactions();
let commitments = transaction.commitments().collect::<Vec<_>>();
assert!(!commitments.is_empty());
for commitment in commitments {
assert_eq!(block.find_transaction_for_commitment(commitment), Some(&transaction));
assert_eq!(transactions.find_transaction_for_commitment(commitment), Some(&transaction));
}
for _ in 0..10 {
let commitment = &rng.gen();
assert_eq!(block.find_transaction_for_commitment(commitment), None);
assert_eq!(transactions.find_transaction_for_commitment(commitment), None);
}
}
#[test]
fn test_find_transition() {
let rng = &mut TestRng::default();
let (block, transaction) = crate::test_helpers::sample_genesis_block_and_transaction(rng);
let transactions = block.transactions();
let transitions = transaction.transitions().collect::<Vec<_>>();
assert!(!transitions.is_empty());
for transition in transitions {
assert_eq!(block.find_transition(transition.id()), Some(transition));
assert_eq!(transactions.find_transition(transition.id()), Some(transition));
assert_eq!(transaction.find_transition(transition.id()), Some(transition));
}
for _ in 0..10 {
let transition_id = &rng.gen();
assert_eq!(block.find_transition(transition_id), None);
assert_eq!(transactions.find_transition(transition_id), None);
assert_eq!(transaction.find_transition(transition_id), None);
}
}
#[test]
fn test_find_transition_for_commitment() {
let rng = &mut TestRng::default();
let (block, transaction) = crate::test_helpers::sample_genesis_block_and_transaction(rng);
let transactions = block.transactions();
let transitions = transaction.transitions().collect::<Vec<_>>();
assert!(!transitions.is_empty());
for transition in transitions {
let commitments = transition.commitments().collect::<Vec<_>>();
assert!(!commitments.is_empty());
for commitment in commitments {
assert_eq!(block.find_transition_for_commitment(commitment), Some(transition));
assert_eq!(transactions.find_transition_for_commitment(commitment), Some(transition));
assert_eq!(transaction.find_transition_for_commitment(commitment), Some(transition));
}
}
for _ in 0..10 {
let commitment = &rng.gen();
assert_eq!(block.find_transition_for_commitment(commitment), None);
assert_eq!(transactions.find_transition_for_commitment(commitment), None);
assert_eq!(transaction.find_transition_for_commitment(commitment), None);
}
}
#[test]
fn test_find_record() {
let rng = &mut TestRng::default();
let (block, transaction) = crate::test_helpers::sample_genesis_block_and_transaction(rng);
let transactions = block.transactions();
let records = transaction.records().collect::<IndexMap<_, _>>();
assert!(!records.is_empty());
for (commitment, record) in records {
assert_eq!(block.find_record(commitment), Some(record));
assert_eq!(transactions.find_record(commitment), Some(record));
assert_eq!(transaction.find_record(commitment), Some(record));
}
for _ in 0..10 {
let commitment = &rng.gen();
assert_eq!(block.find_record(commitment), None);
assert_eq!(transactions.find_record(commitment), None);
assert_eq!(transaction.find_record(commitment), None);
}
}
#[test]
fn test_serde_metadata() {
let metadata = crate::test_helpers::sample_metadata();
let json_metadata = serde_json::to_string(&metadata).unwrap();
let deserialized_metadata: Metadata<CurrentNetwork> = serde_json::from_str(&json_metadata).unwrap();
assert_eq!(metadata, deserialized_metadata);
}
}