use crate::prelude::*;
use anyhow::{anyhow, Result};
use chrono::Utc;
use rand::{CryptoRng, Rng};
use std::{collections::HashMap, sync::atomic::AtomicBool};
#[derive(Clone, Debug)]
pub struct Ledger<N: Network> {
canon_blocks: Blocks<N>,
orphan_blocks: HashMap<u32, Block<N>>,
memory_pool: MemoryPool<N>,
}
impl<N: Network> Ledger<N> {
pub fn new() -> Result<Self> {
Ok(Self {
canon_blocks: Blocks::new()?,
orphan_blocks: Default::default(),
memory_pool: MemoryPool::new(),
})
}
pub fn latest_block_height(&self) -> u32 {
self.canon_blocks.latest_block_height()
}
pub fn latest_block_hash(&self) -> N::BlockHash {
self.canon_blocks.latest_block_hash()
}
pub fn latest_ledger_root(&self) -> N::LedgerRoot {
self.canon_blocks.latest_ledger_root()
}
pub fn latest_block_timestamp(&self) -> Result<i64> {
self.canon_blocks.latest_block_timestamp()
}
pub fn latest_block_difficulty_target(&self) -> Result<u64> {
self.canon_blocks.latest_block_difficulty_target()
}
pub fn latest_cumulative_weight(&self) -> Result<u128> {
self.canon_blocks.latest_cumulative_weight()
}
pub fn latest_block_transactions(&self) -> Result<&Transactions<N>> {
self.canon_blocks.latest_block_transactions()
}
pub fn latest_block(&self) -> Result<Block<N>> {
self.canon_blocks.latest_block()
}
pub fn contains_ledger_root(&self, ledger_root: &N::LedgerRoot) -> bool {
self.canon_blocks.contains_ledger_root(ledger_root)
}
pub fn contains_block_hash(&self, block_hash: &N::BlockHash) -> bool {
self.canon_blocks.contains_block_hash(block_hash)
}
pub fn contains_transaction(&self, transaction: &Transaction<N>) -> bool {
self.canon_blocks.contains_transaction(transaction)
}
pub fn add_next_block(&mut self, block: &Block<N>) -> Result<()> {
self.canon_blocks.add_next(block)?;
Ok(())
}
pub fn add_orphan_block(&mut self, block: &Block<N>) -> Result<()> {
if self.canon_blocks.contains_block_hash(&block.hash()) {
return Err(anyhow!("Orphan block already exists in canon chain"));
}
self.orphan_blocks.insert(block.height(), block.clone());
Ok(())
}
pub fn add_unconfirmed_transaction(&mut self, transaction: &Transaction<N>) -> Result<()> {
if !self.canon_blocks.contains_ledger_root(&transaction.ledger_root()) {
return Err(anyhow!("Transaction references a non-existent ledger root"));
}
for serial_number in transaction.serial_numbers() {
if self.canon_blocks.contains_serial_number(serial_number) {
return Err(anyhow!("Transaction contains a serial number already in existence"));
}
}
for commitment in transaction.commitments() {
if self.canon_blocks.contains_commitment(commitment) {
return Err(anyhow!("Transaction contains a commitment already in existence"));
}
}
self.memory_pool.add_transaction(transaction)?;
Ok(())
}
pub fn mine_next_block<R: Rng + CryptoRng>(
&mut self,
recipient: Address<N>,
is_public: bool,
terminator: &AtomicBool,
rng: &mut R,
) -> Result<Record<N>> {
let previous_block_hash = self.latest_block_hash();
let block_height = self.latest_block_height() + 1;
let block_timestamp = std::cmp::max(Utc::now().timestamp(), self.latest_block_timestamp()?.saturating_add(1));
let difficulty_target = if N::NETWORK_ID == 2 && block_height <= crate::testnet2::V12_UPGRADE_BLOCK_HEIGHT {
Blocks::<N>::compute_difficulty_target(self.latest_block()?.header(), block_timestamp, block_height)
} else if N::NETWORK_ID == 2 {
let anchor_block_header = self
.canon_blocks
.get_block_header(crate::testnet2::V12_UPGRADE_BLOCK_HEIGHT)?;
Blocks::<N>::compute_difficulty_target(anchor_block_header, block_timestamp, block_height)
} else {
Blocks::<N>::compute_difficulty_target(N::genesis_block().header(), block_timestamp, block_height)
};
let cumulative_weight = self
.latest_cumulative_weight()?
.saturating_add((u64::MAX / difficulty_target) as u128);
let amount = Block::<N>::block_reward(block_height);
let (coinbase_transaction, coinbase_record) =
Transaction::<N>::new_coinbase(recipient, amount, is_public, rng)?;
let transactions = Transactions::from(&[vec![coinbase_transaction], self.memory_pool.transactions()].concat())?;
let previous_ledger_root = self.canon_blocks.latest_ledger_root();
let template = BlockTemplate::new(
previous_block_hash,
block_height,
block_timestamp,
difficulty_target,
cumulative_weight,
previous_ledger_root,
transactions,
coinbase_record.clone(),
);
let block = Block::mine(&template, terminator, rng)?;
self.add_next_block(&block)?;
self.memory_pool.clear_all_transactions();
Ok(coinbase_record)
}
pub fn to_ledger_tree(&self) -> &LedgerTree<N> {
self.canon_blocks.to_ledger_tree()
}
pub fn to_ledger_proof(&self, commitment: N::Commitment) -> Result<LedgerProof<N>> {
self.canon_blocks.to_ledger_proof(commitment)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{testnet1::Testnet1, testnet2::Testnet2};
use rand::thread_rng;
#[test]
fn test_new() {
let ledger = Ledger::<Testnet1>::new().unwrap();
assert_eq!(0, ledger.latest_block_height());
let ledger = Ledger::<Testnet2>::new().unwrap();
assert_eq!(0, ledger.latest_block_height());
}
#[test]
fn test_mine_next_block() {
let rng = &mut thread_rng();
{
let mut ledger = Ledger::<Testnet1>::new().unwrap();
let recipient = Account::<Testnet1>::new(rng);
assert_eq!(0, ledger.latest_block_height());
ledger
.mine_next_block(recipient.address(), true, &AtomicBool::new(false), rng)
.unwrap();
assert_eq!(1, ledger.latest_block_height());
}
{
let mut ledger = Ledger::<Testnet2>::new().unwrap();
let recipient = Account::<Testnet2>::new(rng);
assert_eq!(0, ledger.latest_block_height());
ledger
.mine_next_block(recipient.address(), true, &AtomicBool::new(false), rng)
.unwrap();
assert_eq!(1, ledger.latest_block_height());
}
}
}