use std::ops::RangeInclusive;
use miden_node_db::sqlite::{InList, ReadTx};
use miden_node_utils::limiter::{
MAX_RESPONSE_PAYLOAD_BYTES,
QueryParamLimiter,
QueryParamNullifierPrefixLimit,
};
use miden_protocol::block::BlockNumber;
use miden_protocol::note::Nullifier;
use crate::db::NullifierInfo;
use crate::errors::DatabaseError;
const SQL: &str = include_str!("select_nullifiers_by_prefix.sql");
pub(crate) fn select_nullifiers_by_prefix(
tx: &ReadTx<'_>,
prefix_len: u8,
nullifier_prefixes: &[u16],
block_range: RangeInclusive<BlockNumber>,
) -> Result<(Vec<NullifierInfo>, BlockNumber), DatabaseError> {
pub const NULLIFIER_BYTES: usize = 32; pub const BLOCK_NUM_BYTES: usize = 4; pub const ROW_OVERHEAD_BYTES: usize = NULLIFIER_BYTES + BLOCK_NUM_BYTES; pub const MAX_ROWS: usize = MAX_RESPONSE_PAYLOAD_BYTES / ROW_OVERHEAD_BYTES;
const _: () = assert!(
miden_protocol::MAX_INPUT_NOTES_PER_BLOCK <= MAX_ROWS,
"a block's nullifiers must fit in one response page or pagination cannot make progress",
);
assert_eq!(prefix_len, 16, "Only 16-bit prefixes are supported");
if block_range.is_empty() {
return Err(DatabaseError::InvalidBlockRange {
from: *block_range.start(),
to: *block_range.end(),
});
}
QueryParamNullifierPrefixLimit::check(nullifier_prefixes.len())?;
let prefixes = InList::from_values(nullifier_prefixes);
let limit = i64::try_from(MAX_ROWS + 1).expect("limit fits within i64");
let mut nullifiers =
tx.query(SQL, &[&prefixes, block_range.start(), block_range.end(), &limit], |row| {
Ok(NullifierInfo {
nullifier: row.get::<Nullifier>(0)?,
block_num: row.get::<BlockNumber>(1)?,
})
})?;
if let Some(last_block_num) = nullifiers.last().map(|info| info.block_num)
&& nullifiers.len() > MAX_ROWS
{
let last_block_included = last_block_num.parent().ok_or_else(|| {
DatabaseError::DataCorrupted(format!(
"block {last_block_num} holds more than {MAX_ROWS} nullifiers"
))
})?;
nullifiers.retain(|info| info.block_num != last_block_num);
Ok((nullifiers, last_block_included))
} else {
Ok((nullifiers, *block_range.end()))
}
}