use crate::proto::{
compact_formats::{ChainMetadata, CompactBlock, CompactOrchardAction},
service::{BlockId, BlockRange, PoolType},
};
#[cfg(feature = "heavy")]
use zebra_chain::block::Height;
#[cfg(feature = "heavy")]
use zebra_state::HashOrHeight;
#[derive(Debug, PartialEq, Eq)]
pub enum PoolTypeError {
InvalidPoolType,
UnknownPoolType(i32),
}
pub fn pool_types_from_vector(pool_types: &[i32]) -> Result<Vec<PoolType>, PoolTypeError> {
let pools = if pool_types.is_empty() {
PoolTypeFilter::default().to_pool_types_vector()
} else {
let mut pools: Vec<PoolType> = vec![];
for pool in pool_types.iter() {
match PoolType::try_from(*pool) {
Ok(pool_type) => {
if pool_type == PoolType::Invalid {
return Err(PoolTypeError::InvalidPoolType);
} else {
pools.push(pool_type);
}
}
Err(_) => {
return Err(PoolTypeError::UnknownPoolType(*pool));
}
};
}
pools.clone()
};
Ok(pools)
}
pub fn pool_types_into_i32_vec(pool_types: Vec<PoolType>) -> Vec<i32> {
pool_types.iter().map(|p| *p as i32).collect()
}
pub enum GetBlockRangeError {
NoStartHeightProvided,
NoEndHeightProvided,
StartHeightOutOfRange,
EndHeightOutOfRange,
PoolTypeArgumentError(PoolTypeError),
}
pub struct ValidatedBlockRangeRequest {
start: u64,
end: u64,
pool_types: Vec<PoolType>,
}
impl ValidatedBlockRangeRequest {
pub fn new_from_block_range(
request: &BlockRange,
) -> Result<ValidatedBlockRangeRequest, GetBlockRangeError> {
let start = match &request.start {
Some(block_id) => block_id.height,
None => {
return Err(GetBlockRangeError::NoStartHeightProvided);
}
};
let end = match &request.end {
Some(block_id) => block_id.height,
None => {
return Err(GetBlockRangeError::NoEndHeightProvided);
}
};
if u32::try_from(start).is_err() {
return Err(GetBlockRangeError::StartHeightOutOfRange);
}
if u32::try_from(end).is_err() {
return Err(GetBlockRangeError::EndHeightOutOfRange);
}
let pool_types = pool_types_from_vector(&request.pool_types)
.map_err(GetBlockRangeError::PoolTypeArgumentError)?;
Ok(ValidatedBlockRangeRequest {
start,
end,
pool_types,
})
}
pub fn start(&self) -> u64 {
self.start
}
pub fn end(&self) -> u64 {
self.end
}
pub fn pool_types(&self) -> Vec<PoolType> {
self.pool_types.clone()
}
pub fn is_reverse_ordered(&self) -> bool {
self.start > self.end
}
pub fn reverse(&mut self) {
(self.start, self.end) = (self.end, self.start);
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PoolTypeFilter {
include_transparent: bool,
include_sapling: bool,
include_orchard: bool,
include_ironwood: bool,
}
impl std::default::Default for PoolTypeFilter {
fn default() -> Self {
PoolTypeFilter {
include_transparent: false,
include_sapling: true,
include_orchard: true,
include_ironwood: true,
}
}
}
impl PoolTypeFilter {
pub fn includes_all() -> Self {
PoolTypeFilter {
include_transparent: true,
include_sapling: true,
include_orchard: true,
include_ironwood: true,
}
}
pub fn new_from_slice(pool_types: &[i32]) -> Result<Self, PoolTypeError> {
let pool_types = pool_types_from_vector(pool_types)?;
Self::new_from_pool_types(&pool_types)
}
pub fn new_from_pool_types(
pool_types: &Vec<PoolType>,
) -> Result<PoolTypeFilter, PoolTypeError> {
if pool_types.len() > PoolType::Ironwood as usize {
return Err(PoolTypeError::InvalidPoolType);
}
if pool_types.is_empty() {
Ok(Self::default())
} else {
let mut filter = PoolTypeFilter::empty();
for pool_type in pool_types {
match pool_type {
PoolType::Invalid => return Err(PoolTypeError::InvalidPoolType),
PoolType::Transparent => filter.include_transparent = true,
PoolType::Sapling => filter.include_sapling = true,
PoolType::Orchard => filter.include_orchard = true,
PoolType::Ironwood => filter.include_ironwood = true,
}
}
if filter.is_empty() {
Ok(Self::default())
} else {
Ok(filter)
}
}
}
fn empty() -> Self {
Self {
include_transparent: false,
include_sapling: false,
include_orchard: false,
include_ironwood: false,
}
}
fn is_empty(&self) -> bool {
!self.include_transparent
&& !self.include_sapling
&& !self.include_orchard
&& !self.include_ironwood
}
pub fn includes_transparent(&self) -> bool {
self.include_transparent
}
pub fn includes_sapling(&self) -> bool {
self.include_sapling
}
pub fn includes_orchard(&self) -> bool {
self.include_orchard
}
pub fn includes_ironwood(&self) -> bool {
self.include_ironwood
}
pub fn to_pool_types_vector(&self) -> Vec<PoolType> {
let mut pool_types: Vec<PoolType> = Vec::new();
if self.include_transparent {
pool_types.push(PoolType::Transparent);
}
if self.include_sapling {
pool_types.push(PoolType::Sapling);
}
if self.include_orchard {
pool_types.push(PoolType::Orchard);
}
if self.include_ironwood {
pool_types.push(PoolType::Ironwood);
}
pool_types
}
#[allow(dead_code)]
pub(crate) fn from_checked_parts(
include_transparent: bool,
include_sapling: bool,
include_orchard: bool,
include_ironwood: bool,
) -> Self {
PoolTypeFilter {
include_transparent,
include_sapling,
include_orchard,
include_ironwood,
}
}
}
#[cfg(feature = "heavy")]
pub fn blockid_to_hashorheight(block_id: BlockId) -> Option<HashOrHeight> {
<[u8; 32]>::try_from(block_id.hash)
.map(zebra_chain::block::Hash)
.map(HashOrHeight::from)
.or_else(|_| {
block_id
.height
.try_into()
.map(|height| HashOrHeight::Height(Height(height)))
})
.ok()
}
pub fn compact_block_with_pool_types(
mut block: CompactBlock,
pool_types: &[PoolType],
) -> CompactBlock {
if pool_types.is_empty() {
for compact_tx in &mut block.vtx {
compact_tx.vin.clear();
compact_tx.vout.clear();
}
block.vtx.retain(|compact_tx| {
!compact_tx.spends.is_empty()
|| !compact_tx.outputs.is_empty()
|| !compact_tx.actions.is_empty()
|| !compact_tx.ironwood_actions.is_empty()
});
} else {
for compact_tx in &mut block.vtx {
if !pool_types.contains(&PoolType::Transparent) {
compact_tx.vin.clear();
compact_tx.vout.clear();
}
if !pool_types.contains(&PoolType::Sapling) {
compact_tx.spends.clear();
compact_tx.outputs.clear();
}
if !pool_types.contains(&PoolType::Orchard) {
compact_tx.actions.clear();
}
if !pool_types.contains(&PoolType::Ironwood) {
compact_tx.ironwood_actions.clear();
}
}
block.vtx.retain(|compact_tx| {
!compact_tx.vin.is_empty()
|| !compact_tx.vout.is_empty()
|| !compact_tx.spends.is_empty()
|| !compact_tx.outputs.is_empty()
|| !compact_tx.actions.is_empty()
|| !compact_tx.ironwood_actions.is_empty()
});
}
block
}
pub fn compact_block_to_nullifiers(mut block: CompactBlock) -> CompactBlock {
for ctransaction in &mut block.vtx {
ctransaction.outputs = Vec::new();
ctransaction.vin = Vec::new();
ctransaction.vout = Vec::new();
for caction in &mut ctransaction.actions {
*caction = CompactOrchardAction {
nullifier: caction.nullifier.clone(),
..Default::default()
}
}
for caction in &mut ctransaction.ironwood_actions {
*caction = CompactOrchardAction {
nullifier: caction.nullifier.clone(),
..Default::default()
}
}
}
block.chain_metadata = Some(ChainMetadata {
sapling_commitment_tree_size: 0,
orchard_commitment_tree_size: 0,
ironwood_commitment_tree_size: 0,
});
block
}
#[cfg(test)]
mod test {
use crate::proto::{
service::PoolType,
utils::{PoolTypeError, PoolTypeFilter},
};
#[test]
fn test_pool_type_filter_fails_when_invalid() {
let pools = [
PoolType::Transparent,
PoolType::Sapling,
PoolType::Orchard,
PoolType::Invalid,
]
.to_vec();
assert_eq!(
PoolTypeFilter::new_from_pool_types(&pools),
Err(PoolTypeError::InvalidPoolType)
);
}
#[test]
fn test_pool_type_filter_fails_when_too_many_items() {
let pools = [
PoolType::Transparent,
PoolType::Sapling,
PoolType::Orchard,
PoolType::Ironwood,
PoolType::Orchard,
]
.to_vec();
assert_eq!(
PoolTypeFilter::new_from_pool_types(&pools),
Err(PoolTypeError::InvalidPoolType)
);
}
#[test]
fn test_pool_type_filter_t_z_o() {
let pools = [
PoolType::Transparent,
PoolType::Sapling,
PoolType::Orchard,
PoolType::Ironwood,
]
.to_vec();
assert_eq!(
PoolTypeFilter::new_from_pool_types(&pools),
Ok(PoolTypeFilter::from_checked_parts(true, true, true, true))
);
}
#[test]
fn test_pool_type_filter_t() {
let pools = [PoolType::Transparent].to_vec();
assert_eq!(
PoolTypeFilter::new_from_pool_types(&pools),
Ok(PoolTypeFilter::from_checked_parts(
true, false, false, false
))
);
}
#[test]
fn test_pool_type_filter_default() {
assert_eq!(
PoolTypeFilter::new_from_pool_types(&vec![]),
Ok(PoolTypeFilter::default())
);
}
#[test]
fn test_pool_type_filter_includes_all() {
assert_eq!(
PoolTypeFilter::from_checked_parts(true, true, true, true),
PoolTypeFilter::includes_all()
);
}
#[test]
fn empty_pool_types_request_includes_ironwood() {
let pools = crate::proto::utils::pool_types_from_vector(&[]).unwrap();
assert!(pools.contains(&PoolType::Ironwood), "{pools:?}");
let filter = PoolTypeFilter::new_from_slice(&[]).unwrap();
assert!(filter.includes_ironwood());
assert!(filter.includes_sapling());
assert!(filter.includes_orchard());
assert!(!filter.includes_transparent());
}
}