use std::collections::BTreeSet;
use crate::proto::{
compact_formats::{ChainMetadata, CompactBlock, CompactOrchardAction, CompactTx},
service::{BlockId, BlockRange, PoolType},
};
#[cfg(feature = "heavy")]
use zebra_chain::block::Height;
#[cfg(feature = "heavy")]
use zebra_state::HashOrHeight;
const KNOWN_POOLS: [PoolType; 4] = [
PoolType::Transparent,
PoolType::Sapling,
PoolType::Orchard,
PoolType::Ironwood,
];
#[derive(Debug, PartialEq, Eq)]
pub enum PoolTypeError {
InvalidPoolType,
UnknownPoolType(i32),
DuplicatePoolType,
}
pub fn pool_types_from_vector(pool_types: &[i32]) -> Result<Vec<PoolType>, PoolTypeError> {
if pool_types.is_empty() {
return Ok(PoolTypeFilter::default().to_pool_types_vector());
}
pool_types
.iter()
.map(|&raw| match PoolType::try_from(raw) {
Ok(PoolType::Invalid) => Err(PoolTypeError::InvalidPoolType),
Ok(pool_type) => Ok(pool_type),
Err(_) => Err(PoolTypeError::UnknownPoolType(raw)),
})
.collect()
}
pub fn pool_types_into_i32_vec(pool_types: &[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: u32,
end: u32,
filter: PoolTypeFilter,
}
impl ValidatedBlockRangeRequest {
pub fn new_from_block_range(
request: &BlockRange,
) -> Result<ValidatedBlockRangeRequest, GetBlockRangeError> {
let start = provided_height(&request.start, GetBlockRangeError::NoStartHeightProvided)?;
let end = provided_height(&request.end, GetBlockRangeError::NoEndHeightProvided)?;
let start = u32::try_from(start).map_err(|_| GetBlockRangeError::StartHeightOutOfRange)?;
let end = u32::try_from(end).map_err(|_| GetBlockRangeError::EndHeightOutOfRange)?;
let filter = PoolTypeFilter::new_from_slice(&request.pool_types)
.map_err(GetBlockRangeError::PoolTypeArgumentError)?;
Ok(ValidatedBlockRangeRequest { start, end, filter })
}
pub fn start(&self) -> u32 {
self.start
}
pub fn end(&self) -> u32 {
self.end
}
pub fn pool_type_filter(&self) -> &PoolTypeFilter {
&self.filter
}
}
fn provided_height(
endpoint: &Option<BlockId>,
missing: GetBlockRangeError,
) -> Result<u64, GetBlockRangeError> {
endpoint
.as_ref()
.map(|block_id| block_id.height)
.ok_or(missing)
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PoolTypeFilter {
included: BTreeSet<PoolType>,
}
impl std::default::Default for PoolTypeFilter {
fn default() -> Self {
Self::containing(|pool| pool != PoolType::Transparent)
}
}
impl PoolTypeFilter {
pub fn includes_all() -> Self {
Self::containing(|_| true)
}
fn containing(include: impl Fn(PoolType) -> bool) -> Self {
PoolTypeFilter {
included: KNOWN_POOLS.into_iter().filter(|&p| include(p)).collect(),
}
}
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: &[PoolType]) -> Result<PoolTypeFilter, PoolTypeError> {
if pool_types.is_empty() {
return Ok(Self::default());
}
let included = pool_types
.iter()
.map(|&pool_type| match pool_type {
PoolType::Invalid => Err(PoolTypeError::InvalidPoolType),
pool_type => Ok(pool_type),
})
.collect::<Result<BTreeSet<_>, _>>()?;
if included.len() != pool_types.len() {
return Err(PoolTypeError::DuplicatePoolType);
}
Ok(PoolTypeFilter { included })
}
pub fn includes_transparent(&self) -> bool {
self.included.contains(&PoolType::Transparent)
}
pub fn includes_sapling(&self) -> bool {
self.included.contains(&PoolType::Sapling)
}
pub fn includes_orchard(&self) -> bool {
self.included.contains(&PoolType::Orchard)
}
pub fn includes_ironwood(&self) -> bool {
self.included.contains(&PoolType::Ironwood)
}
pub fn to_pool_types_vector(&self) -> Vec<PoolType> {
self.included.iter().copied().collect()
}
#[cfg(test)]
fn from_checked_parts(
include_transparent: bool,
include_sapling: bool,
include_orchard: bool,
include_ironwood: bool,
) -> Self {
let flags = [
include_transparent,
include_sapling,
include_orchard,
include_ironwood,
];
PoolTypeFilter {
included: KNOWN_POOLS
.into_iter()
.zip(flags)
.filter_map(|(pool, included)| included.then_some(pool))
.collect(),
}
}
}
#[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()
}
impl CompactTx {
pub fn has_pool_data(&self) -> bool {
!self.vin.is_empty()
|| !self.vout.is_empty()
|| !self.spends.is_empty()
|| !self.outputs.is_empty()
|| !self.actions.is_empty()
|| !self.ironwood_actions.is_empty()
}
}
pub fn prune_compact_block(mut block: CompactBlock, filter: &PoolTypeFilter) -> CompactBlock {
block.vtx = block
.vtx
.into_iter()
.map(|compact_tx| prune_compact_tx(compact_tx, filter))
.filter(CompactTx::has_pool_data)
.collect();
block
}
fn prune_compact_tx(compact_tx: CompactTx, filter: &PoolTypeFilter) -> CompactTx {
let CompactTx {
index,
txid,
fee,
spends,
outputs,
actions,
ironwood_actions,
vin,
vout,
} = compact_tx;
CompactTx {
index,
txid,
fee,
spends: included_or_empty(filter.includes_sapling(), spends),
outputs: included_or_empty(filter.includes_sapling(), outputs),
actions: included_or_empty(filter.includes_orchard(), actions),
ironwood_actions: included_or_empty(filter.includes_ironwood(), ironwood_actions),
vin: included_or_empty(filter.includes_transparent(), vin),
vout: included_or_empty(filter.includes_transparent(), vout),
}
}
fn included_or_empty<T>(included: bool, items: Vec<T>) -> Vec<T> {
if included {
items
} else {
Vec::new()
}
}
pub fn compact_block_to_nullifiers(mut block: CompactBlock) -> CompactBlock {
block.vtx = block
.vtx
.into_iter()
.map(|compact_tx| CompactTx {
index: compact_tx.index,
txid: compact_tx.txid,
fee: compact_tx.fee,
spends: compact_tx.spends,
outputs: Vec::new(),
actions: nullifiers_only(compact_tx.actions),
ironwood_actions: nullifiers_only(compact_tx.ironwood_actions),
vin: Vec::new(),
vout: Vec::new(),
})
.collect();
block.chain_metadata = Some(ChainMetadata {
sapling_commitment_tree_size: 0,
orchard_commitment_tree_size: 0,
ironwood_commitment_tree_size: 0,
});
block
}
fn nullifiers_only(actions: Vec<CompactOrchardAction>) -> Vec<CompactOrchardAction> {
actions
.into_iter()
.map(|action| CompactOrchardAction {
nullifier: action.nullifier,
..Default::default()
})
.collect()
}
#[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_duplicated() {
let pools = [
PoolType::Transparent,
PoolType::Sapling,
PoolType::Orchard,
PoolType::Ironwood,
PoolType::Orchard,
]
.to_vec();
assert_eq!(
PoolTypeFilter::new_from_pool_types(&pools),
Err(PoolTypeError::DuplicatePoolType)
);
}
#[test]
fn test_pool_type_filter_fails_on_minimal_duplicate() {
assert_eq!(
PoolTypeFilter::new_from_pool_types(&[PoolType::Orchard, PoolType::Orchard]),
Err(PoolTypeError::DuplicatePoolType)
);
}
#[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(&[]),
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());
}
}