use super::validate::ValidTransaction;
use crate::chain::fork_tree;
use alloc::{
collections::{BTreeMap, BTreeSet},
vec::Vec,
};
use core::{fmt, iter};
mod tests;
pub struct Config {
pub transactions_capacity: usize,
pub blocks_capacity: usize,
pub finalized_block_hash: [u8; 32],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TransactionId(usize);
pub struct LightPool<TTx, TBl, TErr> {
transactions: slab::Slab<Transaction<TTx, TErr>>,
transactions_by_inclusion: BTreeMap<([u8; 32], TransactionId), usize>,
included_transactions: BTreeSet<(TransactionId, [u8; 32])>,
transaction_validations: BTreeMap<(TransactionId, [u8; 32]), Result<Validation, TErr>>,
transactions_by_validation: BTreeSet<([u8; 32], TransactionId)>,
by_hash: BTreeSet<([u8; 32], TransactionId)>,
blocks_tree: fork_tree::ForkTree<Block<TBl>>,
blocks_by_id: hashbrown::HashMap<[u8; 32], fork_tree::NodeIndex, fnv::FnvBuildHasher>,
best_block_index: Option<fork_tree::NodeIndex>,
finalized_block_index: Option<fork_tree::NodeIndex>,
blocks_tree_root_hash: [u8; 32],
blocks_tree_root_relative_height: u64,
}
impl<TTx, TBl, TErr> LightPool<TTx, TBl, TErr>
where
TErr: Clone,
{
pub fn new(config: Config) -> Self {
LightPool {
transactions: slab::Slab::with_capacity(config.transactions_capacity),
transactions_by_inclusion: BTreeMap::new(),
included_transactions: BTreeSet::new(),
transaction_validations: BTreeMap::new(),
transactions_by_validation: BTreeSet::new(),
by_hash: BTreeSet::new(),
blocks_tree: fork_tree::ForkTree::with_capacity(config.blocks_capacity),
blocks_by_id: hashbrown::HashMap::with_capacity_and_hasher(
config.blocks_capacity,
Default::default(),
),
best_block_index: None,
finalized_block_index: None,
blocks_tree_root_hash: config.finalized_block_hash,
blocks_tree_root_relative_height: 0,
}
}
pub fn num_transactions(&self) -> usize {
self.transactions.len()
}
pub fn add_unvalidated(&mut self, scale_encoded: Vec<u8>, user_data: TTx) -> TransactionId {
let hash = blake2_hash(scale_encoded.as_ref());
let tx_id = TransactionId(self.transactions.insert(Transaction {
scale_encoded,
user_data,
finalized_chain_validation: None,
best_chain_validation: None,
}));
let _was_inserted = self.by_hash.insert((hash, tx_id));
debug_assert!(_was_inserted);
tx_id
}
#[track_caller]
pub fn remove_transaction(&mut self, id: TransactionId) -> (Vec<u8>, TTx) {
let tx = self.transactions.remove(id.0);
let blocks_included = self
.included_transactions
.range((id, [0; 32])..=(id, [0xff; 32]))
.map(|(_, block)| *block)
.collect::<Vec<_>>();
let blocks_validated = self
.transaction_validations
.range((id, [0; 32])..=(id, [0xff; 32]))
.map(|((_, block), _)| *block)
.collect::<Vec<_>>();
for block_hash in blocks_included {
let _removed = self.included_transactions.remove(&(id, block_hash));
debug_assert!(_removed);
let _removed = self.transactions_by_inclusion.remove(&(block_hash, id));
debug_assert!(_removed.is_some());
}
for block_hash in blocks_validated {
let _removed = self.transaction_validations.remove(&(id, block_hash));
debug_assert!(_removed.is_some());
let _removed = self.transactions_by_validation.remove(&(block_hash, id));
debug_assert!(_removed);
}
let _removed = self.by_hash.remove(&(blake2_hash(&tx.scale_encoded), id));
debug_assert!(_removed);
(tx.scale_encoded, tx.user_data)
}
pub fn unvalidated_transactions(&self) -> impl Iterator<Item = (TransactionId, &TTx)> {
let best_block_relative_height = match self.best_block_index {
Some(idx) => self.blocks_tree.get(idx).unwrap().relative_block_height,
None => self.blocks_tree_root_relative_height,
};
self.transactions
.iter()
.filter(move |(_, tx)| match &tx.best_chain_validation {
None => true,
Some(Ok(v)) => v.longevity_relative_block_height < best_block_relative_height,
Some(Err(_)) => false,
})
.map(move |(tx_id, _)| {
let tx = self.transactions.get(tx_id).unwrap();
(TransactionId(tx_id), &tx.user_data)
})
}
pub fn transactions_iter(&self) -> impl Iterator<Item = (TransactionId, &TTx)> {
self.transactions
.iter()
.map(|(id, tx)| (TransactionId(id), &tx.user_data))
}
pub fn transactions_iter_mut(&mut self) -> impl Iterator<Item = (TransactionId, &mut TTx)> {
self.transactions
.iter_mut()
.map(|(id, tx)| (TransactionId(id), &mut tx.user_data))
}
pub fn transaction_user_data(&self, id: TransactionId) -> Option<&TTx> {
Some(&self.transactions.get(id.0)?.user_data)
}
pub fn transaction_user_data_mut(&mut self, id: TransactionId) -> Option<&mut TTx> {
Some(&mut self.transactions.get_mut(id.0)?.user_data)
}
pub fn scale_encoding(&self, id: TransactionId) -> Option<&[u8]> {
Some(&self.transactions.get(id.0)?.scale_encoded)
}
pub fn find_transaction(&self, scale_encoded: &[u8]) -> impl Iterator<Item = TransactionId> {
let hash = blake2_hash(scale_encoded);
self.by_hash
.range((hash, TransactionId(usize::MIN))..=(hash, TransactionId(usize::MAX)))
.map(|(_, tx_id)| *tx_id)
}
pub fn is_included_best_chain(&self, id: TransactionId) -> bool {
let mut iter = self
.included_transactions
.range((id, [0; 32])..=(id, [0xff; 32]))
.filter(|(_, block_hash)| {
let block_index = *self.blocks_by_id.get(block_hash).unwrap();
self.best_block_index.map_or(false, |best_idx| {
self.blocks_tree.is_ancestor(block_index, best_idx)
})
});
let outcome = iter.next().is_some();
if outcome {
debug_assert!(iter.next().is_none());
}
outcome
}
pub fn is_valid_against_best_block(&self, id: TransactionId) -> bool {
let best_block_relative_height = match self.best_block_index {
Some(idx) => self.blocks_tree.get(idx).unwrap().relative_block_height,
None => self.blocks_tree_root_relative_height,
};
match &self.transactions[id.0].best_chain_validation {
None => false,
Some(Ok(v)) => v.longevity_relative_block_height >= best_block_relative_height,
Some(Err(_)) => false,
}
}
pub fn invalid_transactions_best_block(
&self,
) -> impl Iterator<Item = (TransactionId, &TTx, &TErr)> {
self.transactions
.iter()
.filter_map(move |(tx_id, tx)| match &tx.best_chain_validation {
Some(Err(err)) => Some((TransactionId(tx_id), &tx.user_data, err)),
_ => None,
})
}
pub fn invalid_transactions_finalized_block(
&self,
) -> impl Iterator<Item = (TransactionId, &TTx, &TErr)> {
self.transactions.iter().filter_map(move |(tx_id, tx)| {
match &tx.finalized_chain_validation {
Some((_, Err(err))) => Some((TransactionId(tx_id), &tx.user_data, err)),
_ => None,
}
})
}
pub fn set_validation_result(
&mut self,
id: TransactionId,
block_hash_validated_against: &[u8; 32],
result: Result<ValidTransaction, TErr>,
) {
let block_index = if *block_hash_validated_against == self.blocks_tree_root_hash {
None
} else {
Some(*self.blocks_by_id.get(block_hash_validated_against).unwrap())
};
let block_relative_height = match block_index {
Some(block_index) => {
self.blocks_tree
.get(block_index)
.unwrap()
.relative_block_height
}
None => self.blocks_tree_root_relative_height,
};
assert!(self.transactions.contains(id.0));
let block_is_in_best_chain = match (self.best_block_index, block_index) {
(None, None) => true,
(Some(_), None) => true,
(None, Some(_)) => false,
(Some(b), Some(i)) => self.blocks_tree.is_ancestor(i, b),
};
let block_is_finalized = match (self.finalized_block_index, block_index) {
(None, None) => true,
(Some(_), None) => true,
(None, Some(_)) => false,
(Some(b), Some(i)) => self.blocks_tree.is_ancestor(i, b),
};
debug_assert!(block_is_in_best_chain || !block_is_finalized);
let result = match result {
Err(err) => Err(err),
Ok(v) => Ok(Validation {
longevity_relative_block_height: block_relative_height
.saturating_add(v.longevity.get()),
propagate: v.propagate,
}),
};
if block_is_finalized {
self.transactions[id.0].finalized_chain_validation =
Some((block_relative_height, result.clone()));
}
if block_is_in_best_chain {
self.transactions[id.0].best_chain_validation = Some(result.clone());
}
self.transaction_validations
.insert((id, *block_hash_validated_against), result);
self.transactions_by_validation
.insert((*block_hash_validated_against, id));
}
pub fn add_block(&mut self, hash: [u8; 32], parent_hash: &[u8; 32], user_data: TBl) {
let (parent_index_in_tree, parent_relative_height) =
if *parent_hash == self.blocks_tree_root_hash {
(None, self.blocks_tree_root_relative_height)
} else {
let idx = *self.blocks_by_id.get(parent_hash).unwrap();
(
Some(idx),
self.blocks_tree.get(idx).unwrap().relative_block_height,
)
};
let entry = match self.blocks_by_id.entry(hash) {
hashbrown::hash_map::Entry::Occupied(_) => return,
hashbrown::hash_map::Entry::Vacant(e) => e,
};
let block_index = self.blocks_tree.insert(
parent_index_in_tree,
Block {
hash,
body: if self.transactions.is_empty() {
BodyState::NotNeeded
} else {
BodyState::Needed
},
relative_block_height: parent_relative_height + 1,
user_data,
},
);
entry.insert(block_index);
}
#[must_use]
pub fn set_best_block(&mut self, new_best_block_hash: &[u8; 32]) -> SetBestBlock {
let new_best_block_index = if *new_best_block_hash == self.blocks_tree_root_hash {
None
} else {
Some(*self.blocks_by_id.get(new_best_block_hash).unwrap())
};
let (old_best_to_common_ancestor, common_ancestor_to_new_best) =
match (self.best_block_index, new_best_block_index) {
(Some(old_best_index), Some(new_best_block_index)) => {
let (ascend, descend) = self
.blocks_tree
.ascend_and_descend(old_best_index, new_best_block_index);
(
either::Left(either::Left(ascend)),
either::Left(either::Left(descend)),
)
}
(Some(old_best_index), None) => {
let ascend = self.blocks_tree.node_to_root_path(old_best_index);
let descend = iter::empty::<fork_tree::NodeIndex>();
(either::Left(either::Right(ascend)), either::Right(descend))
}
(None, Some(new_best_block_index)) => {
let ascend = iter::empty::<fork_tree::NodeIndex>();
let descend = self.blocks_tree.root_to_node_path(new_best_block_index);
(either::Right(ascend), either::Left(either::Right(descend)))
}
(None, None) => {
let ascend = iter::empty::<fork_tree::NodeIndex>();
let descend = iter::empty::<fork_tree::NodeIndex>();
(either::Right(ascend), either::Right(descend))
}
};
let mut retracted_transactions = Vec::new();
for to_retract_index in old_best_to_common_ancestor {
let retracted = self.blocks_tree.get(to_retract_index).unwrap();
for ((_, tx_id), index) in self.transactions_by_inclusion.range(
(retracted.hash, TransactionId(usize::MIN))
..=(retracted.hash, TransactionId(usize::MAX)),
) {
retracted_transactions.push((*tx_id, retracted.hash, *index));
}
for (_, tx_id) in self.transactions_by_validation.range(
(retracted.hash, TransactionId(usize::MIN))
..=(retracted.hash, TransactionId(usize::MAX)),
) {
self.transactions[tx_id.0].best_chain_validation = self.transactions[tx_id.0]
.finalized_chain_validation
.as_ref()
.map(|(_, v)| v.clone());
}
}
let mut included_transactions = Vec::new();
for to_include_index in common_ancestor_to_new_best {
let included = self.blocks_tree.get(to_include_index).unwrap();
for ((_, tx_id), index) in self.transactions_by_inclusion.range(
(included.hash, TransactionId(usize::MIN))
..=(included.hash, TransactionId(usize::MAX)),
) {
included_transactions.push((*tx_id, included.hash, *index));
}
for (_, tx_id) in self.transactions_by_validation.range(
(included.hash, TransactionId(usize::MIN))
..=(included.hash, TransactionId(usize::MAX)),
) {
let validation = self
.transaction_validations
.get(&(*tx_id, included.hash))
.unwrap()
.clone();
self.transactions[tx_id.0].best_chain_validation = Some(validation);
}
}
self.best_block_index = new_best_block_index;
SetBestBlock {
retracted_transactions,
included_transactions,
}
}
pub fn has_block(&self, hash: &[u8; 32]) -> bool {
self.blocks_by_id.contains_key(hash)
}
pub fn best_block_hash(&self) -> &[u8; 32] {
match self.best_block_index {
Some(idx) => &self.blocks_tree.get(idx).unwrap().hash,
None => &self.blocks_tree_root_hash,
}
}
pub fn block_user_data(&self, hash: &[u8; 32]) -> Option<&TBl> {
let index = *self.blocks_by_id.get(hash)?;
Some(&self.blocks_tree.get(index).unwrap().user_data)
}
pub fn block_user_data_mut(&mut self, hash: &[u8; 32]) -> Option<&mut TBl> {
let index = *self.blocks_by_id.get(hash)?;
Some(&mut self.blocks_tree.get_mut(index).unwrap().user_data)
}
#[must_use = "`set_block_body` returns the list of transactions that are now included in the chain"]
pub fn set_block_body(
&mut self,
block_hash: &[u8; 32],
body: impl Iterator<Item = impl AsRef<[u8]>>,
) -> impl Iterator<Item = (TransactionId, usize)> {
let block_index = *self.blocks_by_id.get(block_hash).unwrap();
assert!(!matches!(
self.blocks_tree.get_mut(block_index).unwrap().body,
BodyState::Known
));
self.blocks_tree.get_mut(block_index).unwrap().body = BodyState::Known;
let is_in_best_chain = self.best_block_index.map_or(false, |best_block_index| {
self.blocks_tree.is_ancestor(block_index, best_block_index)
});
let mut included_transactions = Vec::new();
for (included_body_index, included_body) in body.into_iter().enumerate() {
let included_body = included_body.as_ref();
let hash = blake2_hash(included_body);
'tx_in_pool: for (_, known_tx_id) in self
.by_hash
.range((hash, TransactionId(usize::MIN))..=(hash, TransactionId(usize::MAX)))
{
let mut now_included = is_in_best_chain;
for (_, existing_included_block) in self
.included_transactions
.range((*known_tx_id, [0x0; 32])..=(*known_tx_id, [0xff; 32]))
.cloned()
.collect::<Vec<_>>()
{
let existing_included_block_idx =
*self.blocks_by_id.get(&existing_included_block).unwrap();
if self
.blocks_tree
.is_ancestor(existing_included_block_idx, block_index)
{
continue 'tx_in_pool;
}
if self
.blocks_tree
.is_ancestor(block_index, existing_included_block_idx)
{
let _was_removed = self
.transactions_by_inclusion
.remove(&(existing_included_block, *known_tx_id));
debug_assert!(_was_removed.is_some());
let _was_removed = self
.included_transactions
.remove(&(*known_tx_id, existing_included_block));
debug_assert!(_was_removed);
if self.best_block_index.map_or(false, |best_block_index| {
self.blocks_tree
.is_ancestor(existing_included_block_idx, best_block_index)
}) {
now_included = false;
}
}
}
let _was_present = self
.transactions_by_inclusion
.insert((*block_hash, *known_tx_id), included_body_index);
debug_assert!(_was_present.is_none());
let _was_included = self
.included_transactions
.insert((*known_tx_id, *block_hash));
debug_assert!(_was_included);
if now_included {
included_transactions.push((*known_tx_id, included_body_index));
}
}
}
included_transactions.into_iter()
}
pub fn missing_block_bodies(&self) -> impl Iterator<Item = (&[u8; 32], &TBl)> {
self.blocks_tree
.iter_unordered()
.filter_map(move |(_, block)| {
if !matches!(block.body, BodyState::Needed) {
return None;
}
Some((&block.hash, &block.user_data))
})
}
pub fn set_finalized_block(
&mut self,
new_finalized_block_hash: &[u8; 32],
) -> impl Iterator<Item = ([u8; 32], TBl)> + use<TTx, TBl, TErr> {
let new_finalized_block_index = if *new_finalized_block_hash == self.blocks_tree_root_hash {
assert!(self.finalized_block_index.is_none());
return Vec::new().into_iter();
} else {
let index = *self.blocks_by_id.get(new_finalized_block_hash).unwrap();
assert!(
self.blocks_tree
.is_ancestor(index, self.best_block_index.unwrap())
);
index
};
{
let old_finalized_to_new_finalized = match self.finalized_block_index {
Some(old_fin_index) => {
let (_ascend, descend) = self
.blocks_tree
.ascend_and_descend(old_fin_index, new_finalized_block_index);
debug_assert_eq!(_ascend.count(), 0);
either::Left(descend)
}
None => {
let iter = self
.blocks_tree
.root_to_node_path(new_finalized_block_index);
either::Right(iter)
}
};
for block_index in old_finalized_to_new_finalized {
let block = self.blocks_tree.get(block_index).unwrap();
let validated_txs = self
.transactions_by_validation
.range(
(block.hash, TransactionId(usize::MIN))
..=(block.hash, TransactionId(usize::MAX)),
)
.map(|(_, tx)| *tx)
.collect::<Vec<_>>();
for tx_id in validated_txs {
let validation = self
.transaction_validations
.get(&(tx_id, block.hash))
.unwrap()
.clone();
self.transactions[tx_id.0].finalized_chain_validation =
Some((block.relative_block_height, validation));
}
}
}
self.finalized_block_index = Some(new_finalized_block_index);
let mut out = Vec::new();
for pruned_block in self.blocks_tree.prune_uncles(new_finalized_block_index) {
debug_assert!(!pruned_block.is_prune_target_ancestor);
let _expected_index = self.blocks_by_id.remove(&pruned_block.user_data.hash);
debug_assert_eq!(_expected_index, Some(pruned_block.index));
let included_txs = self
.transactions_by_inclusion
.range(
(pruned_block.user_data.hash, TransactionId(usize::MIN))
..=(pruned_block.user_data.hash, TransactionId(usize::MAX)),
)
.map(|((_, tx), _)| *tx)
.collect::<Vec<_>>();
for tx_id in included_txs {
let _was_removed = self
.transactions_by_inclusion
.remove(&(pruned_block.user_data.hash, tx_id));
debug_assert!(_was_removed.is_some());
let _was_removed = self
.included_transactions
.remove(&(tx_id, pruned_block.user_data.hash));
debug_assert!(_was_removed);
}
let validated_txs = self
.transactions_by_validation
.range(
(pruned_block.user_data.hash, TransactionId(usize::MIN))
..=(pruned_block.user_data.hash, TransactionId(usize::MAX)),
)
.map(|(_, tx)| *tx)
.collect::<Vec<_>>();
for tx_id in validated_txs {
let _was_removed = self
.transactions_by_validation
.remove(&(pruned_block.user_data.hash, tx_id));
debug_assert!(_was_removed);
let _was_removed = self
.transaction_validations
.remove(&(tx_id, pruned_block.user_data.hash));
debug_assert!(_was_removed.is_some());
}
out.push((
pruned_block.user_data.hash,
pruned_block.user_data.user_data,
));
}
out.into_iter()
}
pub fn prune_finalized_with_body(
&mut self,
) -> impl Iterator<Item = PruneBodyFinalized<TTx, TBl>> + use<TTx, TBl, TErr> {
let finalized_block_index = match self.finalized_block_index {
Some(idx) => idx,
None => return either::Right(iter::empty()),
};
let (num_blocks_to_remove, upmost_to_remove) = {
let search = self
.blocks_tree
.root_to_node_path(finalized_block_index)
.take_while(|idx| {
!matches!(self.blocks_tree.get(*idx).unwrap().body, BodyState::Needed)
})
.enumerate()
.map(|(n, b)| (n + 1, b))
.last();
match search {
Some(idx) => idx,
None => return either::Right(iter::empty()),
}
};
if upmost_to_remove == finalized_block_index {
self.finalized_block_index = None;
self.blocks_tree_root_hash = self.blocks_tree.get(upmost_to_remove).unwrap().hash;
}
let mut return_value = Vec::with_capacity(num_blocks_to_remove);
for pruned in self.blocks_tree.prune_ancestors(upmost_to_remove) {
debug_assert!(pruned.is_prune_target_ancestor);
let _removed = self.blocks_by_id.remove(&pruned.user_data.hash);
debug_assert_eq!(_removed, Some(pruned.index));
let included_transactions_ids = self
.transactions_by_inclusion
.range(
(pruned.user_data.hash, TransactionId(usize::MIN))
..=(pruned.user_data.hash, TransactionId(usize::MAX)),
)
.map(|((_, tx_id), index)| (*tx_id, *index))
.collect::<Vec<_>>();
let mut included_transactions = Vec::with_capacity(included_transactions_ids.len());
for (tx_id, index_in_block) in &included_transactions_ids {
let tx = self.transactions.remove(tx_id.0);
let blocks_included = self
.included_transactions
.range((*tx_id, [0; 32])..=(*tx_id, [0xff; 32]))
.map(|(_, block)| *block)
.collect::<Vec<_>>();
let blocks_validated = self
.transaction_validations
.range((*tx_id, [0; 32])..=(*tx_id, [0xff; 32]))
.map(|((_, block), _)| *block)
.collect::<Vec<_>>();
for block_hash in blocks_included {
let _removed = self.included_transactions.remove(&(*tx_id, block_hash));
debug_assert!(_removed);
let _removed = self.transactions_by_inclusion.remove(&(block_hash, *tx_id));
debug_assert!(_removed.is_some());
}
for block_hash in blocks_validated {
let _removed = self.transaction_validations.remove(&(*tx_id, block_hash));
debug_assert!(_removed.is_some());
let _removed = self
.transactions_by_validation
.remove(&(block_hash, *tx_id));
debug_assert!(_removed);
}
let _removed = self
.by_hash
.remove(&(blake2_hash(&tx.scale_encoded), *tx_id));
debug_assert!(_removed);
included_transactions.push(RemovedTransaction {
id: *tx_id,
index_in_block: *index_in_block,
scale_encoding: tx.scale_encoded,
user_data: tx.user_data,
});
}
let validated_txs = self
.transactions_by_validation
.range(
(pruned.user_data.hash, TransactionId(usize::MIN))
..=(pruned.user_data.hash, TransactionId(usize::MAX)),
)
.map(|(_, tx)| *tx)
.collect::<Vec<_>>();
for tx_id in validated_txs {
let _was_removed = self
.transactions_by_validation
.remove(&(pruned.user_data.hash, tx_id));
debug_assert!(_was_removed);
let _was_removed = self
.transaction_validations
.remove(&(tx_id, pruned.user_data.hash));
debug_assert!(_was_removed.is_some());
}
return_value.push(PruneBodyFinalized {
block_hash: pruned.user_data.hash,
included_transactions,
user_data: pruned.user_data.user_data,
});
}
if self.best_block_index.unwrap() == upmost_to_remove {
self.best_block_index = None;
}
either::Left(return_value.into_iter())
}
pub fn oldest_block_finality_lag(&self) -> usize {
if let Some(finalized_block_index) = self.finalized_block_index {
self.blocks_tree
.root_to_node_path(finalized_block_index)
.count()
} else {
0
}
}
}
impl<TTx: fmt::Debug, TBl, TErr> fmt::Debug for LightPool<TTx, TBl, TErr> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_list()
.entries(
self.transactions
.iter()
.map(|t| (TransactionId(t.0), &t.1.user_data)),
)
.finish()
}
}
pub struct PruneBodyFinalized<TTx, TBl> {
pub block_hash: [u8; 32],
pub user_data: TBl,
pub included_transactions: Vec<RemovedTransaction<TTx>>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RemovedTransaction<TTx> {
pub id: TransactionId,
pub index_in_block: usize,
pub scale_encoding: Vec<u8>,
pub user_data: TTx,
}
#[derive(Debug, Clone)]
pub struct SetBestBlock {
pub retracted_transactions: Vec<(TransactionId, [u8; 32], usize)>,
pub included_transactions: Vec<(TransactionId, [u8; 32], usize)>,
}
struct Transaction<TTx, TErr> {
scale_encoded: Vec<u8>,
user_data: TTx,
finalized_chain_validation: Option<(u64, Result<Validation, TErr>)>,
best_chain_validation: Option<Result<Validation, TErr>>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct Validation {
longevity_relative_block_height: u64,
propagate: bool,
}
struct Block<TBl> {
relative_block_height: u64,
hash: [u8; 32],
body: BodyState,
user_data: TBl,
}
enum BodyState {
Needed,
NotNeeded,
Known,
}
fn blake2_hash(bytes: &[u8]) -> [u8; 32] {
<[u8; 32]>::try_from(blake2_rfc::blake2b::blake2b(32, &[], bytes).as_bytes()).unwrap()
}