use crate::{
atomic_write_batch,
cow_to_cloned,
cow_to_copied,
ledger::{
map::{memory_map::MemoryMap, Map, MapRead},
store::{
TransactionMemory,
TransactionStorage,
TransactionStore,
TransitionMemory,
TransitionStorage,
TransitionStore,
},
Block,
Header,
Signature,
Transactions,
},
};
use console::network::prelude::*;
use anyhow::Result;
use core::marker::PhantomData;
use std::borrow::Cow;
macro_rules! bail_with_block {
($message:expr, $self:ident, $hash:expr) => {{
let message = format!($message);
anyhow::bail!("{message} for block '{:?}' ('{}')", $self.get_block_height(&$hash)?, $hash);
}};
}
pub trait BlockStorage<N: Network>: Clone + Sync {
type IDMap: for<'a> Map<'a, u32, N::BlockHash>;
type ReverseIDMap: for<'a> Map<'a, N::BlockHash, u32>;
type HeaderMap: for<'a> Map<'a, N::BlockHash, Header<N>>;
type TransactionsMap: for<'a> Map<'a, N::BlockHash, Vec<N::TransactionID>>;
type ReverseTransactionsMap: for<'a> Map<'a, N::TransactionID, N::BlockHash>;
type TransactionStorage: TransactionStorage<N, TransitionStorage = Self::TransitionStorage>;
type TransitionStorage: TransitionStorage<N>;
type SignatureMap: for<'a> Map<'a, N::BlockHash, Signature<N>>;
fn open() -> Result<Self>;
fn id_map(&self) -> &Self::IDMap;
fn reverse_id_map(&self) -> &Self::ReverseIDMap;
fn header_map(&self) -> &Self::HeaderMap;
fn transactions_map(&self) -> &Self::TransactionsMap;
fn reverse_transactions_map(&self) -> &Self::ReverseTransactionsMap;
fn transaction_store(&self) -> &TransactionStore<N, Self::TransactionStorage>;
fn signature_map(&self) -> &Self::SignatureMap;
fn start_atomic(&self) {
self.id_map().start_atomic();
self.reverse_id_map().start_atomic();
self.header_map().start_atomic();
self.transactions_map().start_atomic();
self.reverse_transactions_map().start_atomic();
self.transaction_store().start_atomic();
self.signature_map().start_atomic();
}
fn is_atomic_in_progress(&self) -> bool {
self.id_map().is_atomic_in_progress()
|| self.reverse_id_map().is_atomic_in_progress()
|| self.header_map().is_atomic_in_progress()
|| self.transactions_map().is_atomic_in_progress()
|| self.reverse_transactions_map().is_atomic_in_progress()
|| self.transaction_store().is_atomic_in_progress()
|| self.signature_map().is_atomic_in_progress()
}
fn abort_atomic(&self) {
self.id_map().abort_atomic();
self.reverse_id_map().abort_atomic();
self.header_map().abort_atomic();
self.transactions_map().abort_atomic();
self.reverse_transactions_map().abort_atomic();
self.transaction_store().abort_atomic();
self.signature_map().abort_atomic();
}
fn finish_atomic(&self) -> Result<()> {
self.id_map().finish_atomic()?;
self.reverse_id_map().finish_atomic()?;
self.header_map().finish_atomic()?;
self.transactions_map().finish_atomic()?;
self.reverse_transactions_map().finish_atomic()?;
self.transaction_store().finish_atomic()?;
self.signature_map().finish_atomic()
}
fn insert(&self, block: &Block<N>) -> Result<()> {
atomic_write_batch!(self, {
self.id_map().insert(block.height(), block.hash())?;
self.reverse_id_map().insert(block.hash(), block.height())?;
self.header_map().insert(block.hash(), *block.header())?;
self.transactions_map().insert(block.hash(), block.transaction_ids().copied().collect())?;
for transaction in block.transactions().values() {
self.reverse_transactions_map().insert(transaction.id(), block.hash())?;
self.transaction_store().insert(transaction)?;
}
self.signature_map().insert(block.hash(), *block.signature())?;
Ok(())
});
Ok(())
}
fn remove(&self, block_hash: &N::BlockHash) -> Result<()> {
let height = match self.get_block_height(block_hash)? {
Some(height) => height,
None => bail!("Failed to remove block: missing block height for block hash '{block_hash}'"),
};
let transaction_ids = match self.transactions_map().get(block_hash)? {
Some(transaction_ids) => transaction_ids,
None => bail!("Failed to remove block: missing transactions for block '{height}' ('{block_hash}')"),
};
atomic_write_batch!(self, {
self.id_map().remove(&height)?;
self.reverse_id_map().remove(block_hash)?;
self.header_map().remove(block_hash)?;
self.transactions_map().remove(block_hash)?;
for transaction_id in transaction_ids.iter() {
self.reverse_transactions_map().remove(transaction_id)?;
self.transaction_store().remove(transaction_id)?;
}
self.signature_map().remove(block_hash)?;
Ok(())
});
Ok(())
}
fn find_block_hash(&self, transaction_id: &N::TransactionID) -> Result<Option<N::BlockHash>> {
match self.reverse_transactions_map().get(transaction_id)? {
Some(block_hash) => Ok(Some(cow_to_copied!(block_hash))),
None => Ok(None),
}
}
fn get_previous_block_hash(&self, height: u32) -> Result<Option<N::BlockHash>> {
match height.is_zero() {
true => Ok(Some(N::BlockHash::default())),
false => match self.id_map().get(&(height - 1))? {
Some(block_hash) => Ok(Some(cow_to_copied!(block_hash))),
None => Ok(None),
},
}
}
fn get_block_hash(&self, height: u32) -> Result<Option<N::BlockHash>> {
match self.id_map().get(&height)? {
Some(block_hash) => Ok(Some(cow_to_copied!(block_hash))),
None => Ok(None),
}
}
fn get_block_height(&self, block_hash: &N::BlockHash) -> Result<Option<u32>> {
match self.reverse_id_map().get(block_hash)? {
Some(height) => Ok(Some(cow_to_copied!(height))),
None => Ok(None),
}
}
fn get_block_header(&self, block_hash: &N::BlockHash) -> Result<Option<Header<N>>> {
match self.header_map().get(block_hash)? {
Some(header) => Ok(Some(cow_to_cloned!(header))),
None => Ok(None),
}
}
fn get_block_transactions(&self, block_hash: &N::BlockHash) -> Result<Option<Transactions<N>>> {
let transaction_ids = match self.transactions_map().get(block_hash)? {
Some(transaction_ids) => transaction_ids,
None => return Ok(None),
};
let transactions = transaction_ids
.iter()
.map(|transaction_id| match self.transaction_store().get_transaction(transaction_id) {
Ok(Some(transaction)) => Ok(transaction),
Ok(None) => bail_with_block!("Missing transaction '{transaction_id}'", self, block_hash),
Err(err) => Err(err),
})
.collect::<Result<Vec<_>>>()?;
Ok(Some(Transactions::from(&transactions)))
}
fn get_block_signature(&self, block_hash: &N::BlockHash) -> Result<Option<Signature<N>>> {
match self.signature_map().get(block_hash)? {
Some(signature) => Ok(Some(cow_to_cloned!(signature))),
None => Ok(None),
}
}
fn get_block(&self, block_hash: &N::BlockHash) -> Result<Option<Block<N>>> {
let height = match self.get_block_height(block_hash)? {
Some(height) => height,
None => return Ok(None),
};
let header = match self.get_block_header(block_hash)? {
Some(header) => header,
None => bail!("Missing block header for block {height} ('{block_hash}')"),
};
if header.height() != height {
bail!("Mismatching block height for block {height} ('{block_hash}')")
}
let previous_hash = match self.get_previous_block_hash(height)? {
Some(previous_block_hash) => previous_block_hash,
None => bail!("Missing previous block hash for block {height} ('{block_hash}')"),
};
let transactions = match self.get_block_transactions(block_hash)? {
Some(transactions) => transactions,
None => bail!("Missing transactions for block {height} ('{block_hash}')"),
};
let signature = match self.get_block_signature(block_hash)? {
Some(signature) => signature,
None => bail!("Missing signature for block {height} ('{block_hash}')"),
};
Ok(Some(Block::from(previous_hash, header, transactions, signature)?))
}
}
#[derive(Clone)]
pub struct BlockMemory<N: Network> {
id_map: MemoryMap<u32, N::BlockHash>,
reverse_id_map: MemoryMap<N::BlockHash, u32>,
header_map: MemoryMap<N::BlockHash, Header<N>>,
transactions_map: MemoryMap<N::BlockHash, Vec<N::TransactionID>>,
reverse_transactions_map: MemoryMap<N::TransactionID, N::BlockHash>,
transaction_store: TransactionStore<N, TransactionMemory<N>>,
signature_map: MemoryMap<N::BlockHash, Signature<N>>,
}
#[rustfmt::skip]
impl<N: Network> BlockStorage<N> for BlockMemory<N> {
type IDMap = MemoryMap<u32, N::BlockHash>;
type ReverseIDMap = MemoryMap<N::BlockHash, u32>;
type HeaderMap = MemoryMap<N::BlockHash, Header<N>>;
type TransactionsMap = MemoryMap<N::BlockHash, Vec<N::TransactionID>>;
type ReverseTransactionsMap = MemoryMap<N::TransactionID, N::BlockHash>;
type TransactionStorage = TransactionMemory<N>;
type TransitionStorage = TransitionMemory<N>;
type SignatureMap = MemoryMap<N::BlockHash, Signature<N>>;
fn open() -> Result<Self> {
let transition_store = TransitionStore::<N, TransitionMemory<N>>::open()?;
let transaction_store = TransactionStore::<N, TransactionMemory<N>>::open(transition_store)?;
Ok(Self {
id_map: MemoryMap::default(),
reverse_id_map: MemoryMap::default(),
header_map: MemoryMap::default(),
transactions_map: MemoryMap::default(),
reverse_transactions_map: MemoryMap::default(),
transaction_store,
signature_map: MemoryMap::default(),
})
}
fn id_map(&self) -> &Self::IDMap {
&self.id_map
}
fn reverse_id_map(&self) -> &Self::ReverseIDMap {
&self.reverse_id_map
}
fn header_map(&self) -> &Self::HeaderMap {
&self.header_map
}
fn transactions_map(&self) -> &Self::TransactionsMap {
&self.transactions_map
}
fn reverse_transactions_map(&self) -> &Self::ReverseTransactionsMap {
&self.reverse_transactions_map
}
fn transaction_store(&self) -> &TransactionStore<N, Self::TransactionStorage> {
&self.transaction_store
}
fn signature_map(&self) -> &Self::SignatureMap {
&self.signature_map
}
}
#[derive(Clone)]
pub struct BlockStore<N: Network, B: BlockStorage<N>> {
storage: B,
_phantom: PhantomData<N>,
}
impl<N: Network, B: BlockStorage<N>> BlockStore<N, B> {
pub fn open() -> Result<Self> {
let storage = B::open()?;
Ok(Self { storage, _phantom: PhantomData })
}
pub fn from(storage: B) -> Self {
Self { storage, _phantom: PhantomData }
}
pub fn insert(&self, block: &Block<N>) -> Result<()> {
self.storage.insert(block)
}
pub fn remove(&self, block_hash: &N::BlockHash) -> Result<()> {
self.storage.remove(block_hash)
}
pub fn transaction_store(&self) -> &TransactionStore<N, B::TransactionStorage> {
self.storage.transaction_store()
}
pub fn transition_store(&self) -> &TransitionStore<N, B::TransitionStorage> {
self.storage.transaction_store().transition_store()
}
pub fn start_atomic(&self) {
self.storage.start_atomic();
}
pub fn abort_atomic(&self) {
self.storage.abort_atomic();
}
pub fn finish_atomic(&self) -> Result<()> {
self.storage.finish_atomic()
}
}
impl<N: Network, B: BlockStorage<N>> BlockStore<N, B> {
pub fn get_previous_block_hash(&self, height: u32) -> Result<Option<N::BlockHash>> {
self.storage.get_previous_block_hash(height)
}
pub fn get_block_hash(&self, height: u32) -> Result<Option<N::BlockHash>> {
self.storage.get_block_hash(height)
}
pub fn get_block_height(&self, block_hash: &N::BlockHash) -> Result<Option<u32>> {
self.storage.get_block_height(block_hash)
}
pub fn get_block_header(&self, block_hash: &N::BlockHash) -> Result<Option<Header<N>>> {
self.storage.get_block_header(block_hash)
}
pub fn get_block_transactions(&self, block_hash: &N::BlockHash) -> Result<Option<Transactions<N>>> {
self.storage.get_block_transactions(block_hash)
}
pub fn get_block_signature(&self, block_hash: &N::BlockHash) -> Result<Option<Signature<N>>> {
self.storage.get_block_signature(block_hash)
}
pub fn get_block(&self, block_hash: &N::BlockHash) -> Result<Option<Block<N>>> {
self.storage.get_block(block_hash)
}
}
impl<N: Network, B: BlockStorage<N>> BlockStore<N, B> {
pub fn find_block_hash(&self, transaction_id: &N::TransactionID) -> Result<Option<N::BlockHash>> {
self.storage.find_block_hash(transaction_id)
}
}
impl<N: Network, B: BlockStorage<N>> BlockStore<N, B> {
pub fn contains_block_height(&self, height: u32) -> Result<bool> {
self.storage.id_map().contains_key(&height)
}
pub fn contains_block_hash(&self, block_hash: &N::BlockHash) -> Result<bool> {
self.storage.reverse_id_map().contains_key(block_hash)
}
}
impl<N: Network, B: BlockStorage<N>> BlockStore<N, B> {
pub fn heights(&self) -> impl '_ + Iterator<Item = Cow<'_, u32>> {
self.storage.id_map().keys()
}
pub fn hashes(&self) -> impl '_ + Iterator<Item = Cow<'_, N::BlockHash>> {
self.storage.reverse_id_map().keys()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_insert_get_remove() {
let block = crate::ledger::test_helpers::sample_genesis_block();
let block_hash = block.hash();
let block_store = BlockStore::<_, BlockMemory<_>>::open().unwrap();
let candidate = block_store.get_block(&block_hash).unwrap();
assert_eq!(None, candidate);
block_store.insert(&block).unwrap();
let candidate = block_store.get_block(&block_hash).unwrap();
assert_eq!(Some(block), candidate);
block_store.remove(&block_hash).unwrap();
let candidate = block_store.get_block(&block_hash).unwrap();
assert_eq!(None, candidate);
}
#[test]
fn test_find_block_hash() {
let block = crate::ledger::test_helpers::sample_genesis_block();
let block_hash = block.hash();
assert!(block.transactions().len() > 0, "This test must be run with at least one transaction.");
let block_store = BlockStore::<_, BlockMemory<_>>::open().unwrap();
let candidate = block_store.get_block(&block_hash).unwrap();
assert_eq!(None, candidate);
for transaction_id in block.transaction_ids() {
let candidate = block_store.find_block_hash(transaction_id).unwrap();
assert_eq!(None, candidate);
}
block_store.insert(&block).unwrap();
for transaction_id in block.transaction_ids() {
let candidate = block_store.find_block_hash(transaction_id).unwrap();
assert_eq!(Some(block_hash), candidate);
}
block_store.remove(&block_hash).unwrap();
for transaction_id in block.transaction_ids() {
let candidate = block_store.find_block_hash(transaction_id).unwrap();
assert_eq!(None, candidate);
}
}
}