use std::mem::size_of;
use std::ops::RangeInclusive;
use miden_node_db::sqlite::ReadTx;
use miden_node_utils::limiter::MAX_RESPONSE_PAYLOAD_BYTES;
use miden_protocol::Word;
use miden_protocol::account::AccountId;
use miden_protocol::asset::{Asset, AssetId};
use miden_protocol::block::BlockNumber;
use crate::db::AccountVaultValue;
use crate::errors::DatabaseError;
const SQL: &str = include_str!("select_account_vault_assets.sql");
pub(crate) fn select_account_vault_assets(
tx: &ReadTx<'_>,
account_id: AccountId,
block_range: RangeInclusive<BlockNumber>,
) -> Result<(BlockNumber, Vec<AccountVaultValue>), DatabaseError> {
const ROW_OVERHEAD_BYTES: usize = 2 * size_of::<Word>() + size_of::<u32>(); const MAX_ROWS: usize = MAX_RESPONSE_PAYLOAD_BYTES / ROW_OVERHEAD_BYTES;
if !account_id.is_public() {
return Err(DatabaseError::AccountNotPublic(account_id));
}
if block_range.is_empty() {
return Err(DatabaseError::InvalidBlockRange {
from: *block_range.start(),
to: *block_range.end(),
});
}
let limit = i64::try_from(MAX_ROWS + 1).expect("should fit within i64");
let rows =
tx.query(SQL, &[&account_id, block_range.start(), block_range.end(), &limit], |row| {
Ok((row.get::<BlockNumber>(0)?, row.get::<Word>(1)?, row.get::<Option<Asset>>(2)?))
})?;
let mut values = rows
.into_iter()
.map(|(block_num, vault_key, asset)| {
Ok(AccountVaultValue {
block_num,
vault_key: AssetId::try_from(vault_key)?,
asset,
})
})
.collect::<Result<Vec<_>, DatabaseError>>()?;
let last_block_included = if let Some(last_block_num) = values.last().map(|v| v.block_num)
&& values.len() > MAX_ROWS
{
let complete_len = values.partition_point(|v| v.block_num < last_block_num);
values.truncate(complete_len);
values.last().map_or(*block_range.start(), |v| v.block_num)
} else {
*block_range.end()
};
Ok((last_block_included, values))
}