use miden_node_db::sqlite::ReadTx;
use miden_node_db::{DatabaseError, SqlTypeConvert};
use miden_protocol::account::AccountId;
use miden_protocol::block::BlockNumber;
use miden_protocol::note::{Note, Nullifier};
use miden_standards::note::AccountTargetNetworkNote;
use crate::db::eligibility::{has_backoff_passed, hint_floor, note_recheck_block};
const SQL: &str = include_str!("available_notes.sql");
pub struct AvailableNotes {
pub eligible: Vec<AccountTargetNetworkNote>,
pub stale_eligibility: Vec<(Nullifier, BlockNumber)>,
}
#[expect(clippy::cast_possible_wrap)]
pub fn available_notes(
tx: &ReadTx<'_>,
account_id: AccountId,
block_num: BlockNumber,
max_attempts: usize,
) -> Result<AvailableNotes, DatabaseError> {
let rows =
tx.query(SQL, &[&account_id, &(max_attempts as i64), &block_num.to_raw_sql()], |row| {
Ok((row.get::<Note>(0)?, row.get::<i64>(1)?, row.get::<Option<i64>>(2)?))
})?;
let mut eligible = Vec::new();
let mut stale_eligibility = Vec::new();
for (note, attempt_count, last_attempt) in rows {
#[expect(clippy::cast_sign_loss)]
let attempt_count = attempt_count as usize;
let last_attempt = last_attempt.map(BlockNumber::from_raw_sql).transpose()?;
let note = AccountTargetNetworkNote::new(note).map_err(|source| {
DatabaseError::deserialization("failed to convert to network note", source)
})?;
let hint = note.execution_hint();
let hint_ok = block_num >= hint_floor(hint);
let backoff_ok = has_backoff_passed(block_num, last_attempt, attempt_count);
if hint_ok && backoff_ok {
eligible.push(note);
} else {
let recheck = note_recheck_block(
hint,
block_num,
last_attempt,
attempt_count,
backoff_ok,
hint_ok,
);
stale_eligibility.push((note.as_note().nullifier(), recheck));
}
}
Ok(AvailableNotes { eligible, stale_eligibility })
}