use alloc::vec::Vec;
use keetanetwork_crypto::hash::BlockHash;
use num_bigint::BigInt;
use crate::block::{BlockData, BlockPurpose, BlockVersion, UnsignedBlock};
use crate::error::{BlockError, BlockField};
use crate::operation::Operation;
use crate::signer::{AccountRef, Signer};
use crate::time::BlockTime;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Previous {
Hash(BlockHash),
Opening,
}
#[derive(Debug, Clone, Default)]
pub struct BlockBuilder {
version: Option<BlockVersion>,
purpose: Option<BlockPurpose>,
network: Option<BigInt>,
subnet: Option<BigInt>,
idempotent: Option<Vec<u8>>,
date: Option<BlockTime>,
account: Option<AccountRef>,
signer: Option<Signer>,
previous: Option<Previous>,
operations: Vec<Operation>,
}
impl BlockBuilder {
pub fn with_version(mut self, version: BlockVersion) -> Self {
self.version = Some(version);
self
}
pub fn with_purpose(mut self, purpose: BlockPurpose) -> Self {
self.purpose = Some(purpose);
self
}
pub fn with_network(mut self, network: impl Into<BigInt>) -> Self {
self.network = Some(network.into());
self
}
pub fn with_subnet(mut self, subnet: impl Into<BigInt>) -> Self {
self.subnet = Some(subnet.into());
self
}
pub fn with_idempotent(mut self, idempotent: impl Into<Vec<u8>>) -> Self {
self.idempotent = Some(idempotent.into());
self
}
pub fn with_date(mut self, date: BlockTime) -> Self {
self.date = Some(date);
self
}
pub fn with_account(mut self, account: impl Into<AccountRef>) -> Self {
self.account = Some(account.into());
self
}
pub fn with_signer(mut self, signer: impl Into<Signer>) -> Self {
self.signer = Some(signer.into());
self
}
pub fn with_previous(mut self, previous: BlockHash) -> Self {
self.previous = Some(Previous::Hash(previous));
self
}
pub fn as_opening(mut self) -> Self {
self.previous = Some(Previous::Opening);
self
}
pub fn with_operation(mut self, operation: impl Into<Operation>) -> Self {
self.operations.push(operation.into());
self
}
pub fn with_operations(mut self, operations: impl IntoIterator<Item = Operation>) -> Self {
self.operations.extend(operations);
self
}
pub fn build(self) -> Result<UnsignedBlock, BlockError> {
let account = self
.account
.ok_or(BlockError::MissingField { field: BlockField::Account })?;
let network = self
.network
.ok_or(BlockError::MissingField { field: BlockField::Network })?;
let previous = self
.previous
.ok_or(BlockError::MissingField { field: BlockField::Previous })?;
let previous = match previous {
Previous::Hash(hash) => hash,
Previous::Opening => account.to_opening_hash(),
};
let signer = match self.signer {
Some(signer) => signer,
None => Signer::Single(account.clone()),
};
let date = match self.date {
Some(date) => date,
#[cfg(feature = "std")]
None => BlockTime::now(),
#[cfg(not(feature = "std"))]
None => return Err(BlockError::MissingField { field: BlockField::Date }),
};
let data = BlockData {
version: self.version.unwrap_or(BlockVersion::V2),
purpose: self.purpose.unwrap_or(BlockPurpose::Generic),
network,
subnet: self.subnet,
idempotent: self.idempotent,
date,
account,
signer,
previous,
operations: self.operations,
};
data.try_into()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::testing::{generate_ed25519_ref, valid_block_builder};
#[test]
fn test_missing_account_rejected() {
let result = BlockBuilder::default()
.with_network(0u8)
.as_opening()
.build();
assert!(matches!(result, Err(BlockError::MissingField { field: BlockField::Account })));
}
#[test]
fn test_missing_network_rejected() {
let result = BlockBuilder::default()
.with_account(generate_ed25519_ref(1))
.as_opening()
.build();
assert!(matches!(result, Err(BlockError::MissingField { field: BlockField::Network })));
}
#[test]
fn test_missing_previous_rejected() {
let result = BlockBuilder::default()
.with_network(0u8)
.with_account(generate_ed25519_ref(1))
.build();
assert!(matches!(result, Err(BlockError::MissingField { field: BlockField::Previous })));
}
#[test]
fn test_defaults() -> Result<(), BlockError> {
let unsigned = valid_block_builder().build()?;
let data = unsigned.data();
assert_eq!(data.version(), BlockVersion::V2);
assert_eq!(data.purpose(), BlockPurpose::Generic);
assert_eq!(*data.previous(), data.account_opening_hash());
assert!(matches!(data.signer(), Signer::Single(signer) if signer.to_string() == data.account().to_string()));
Ok(())
}
#[test]
fn test_explicit_previous() -> Result<(), BlockError> {
let previous = BlockHash::from([0x11u8; 32]);
let unsigned = valid_block_builder().with_previous(previous).build()?;
assert_eq!(*unsigned.data().previous(), previous);
Ok(())
}
}