use miden_node_db::sqlite::WriteTx;
use miden_node_db::{DatabaseError, SqlTypeConvert};
use miden_node_tracing::ErrorReport;
use miden_protocol::block::BlockNumber;
use miden_protocol::note::{Note, Nullifier};
use miden_standards::note::AccountTargetNetworkNote;
use crate::NoteError;
use crate::db::eligibility::{backoff_ready_block, hint_floor};
const SQL: &str = include_str!("note_failed.sql");
const SELECT_ATTEMPT_STATE_SQL: &str = include_str!("select_note_attempt_state.sql");
pub fn notes_failed(
tx: &WriteTx<'_>,
failed_notes: &[(Nullifier, NoteError)],
block_num: BlockNumber,
) -> Result<(), DatabaseError> {
let block_num_val = block_num.to_raw_sql();
for (nullifier, error) in failed_notes {
let eligible_from = eligibility_after_failure(tx, nullifier, block_num)?;
let error_report = error.as_report();
tx.execute(SQL, &[nullifier, &block_num_val, &error_report, &eligible_from.to_raw_sql()])?;
}
Ok(())
}
fn eligibility_after_failure(
tx: &WriteTx<'_>,
nullifier: &Nullifier,
block_num: BlockNumber,
) -> Result<BlockNumber, DatabaseError> {
#[expect(clippy::cast_sign_loss)]
let (attempt_count, note) = tx
.query(SELECT_ATTEMPT_STATE_SQL, &[nullifier], |row| {
Ok((row.get::<i64>(0)? as usize, row.get::<Note>(1)?))
})?
.into_iter()
.next()
.expect("a failed note must have a row");
let note = AccountTargetNetworkNote::new(note).map_err(|source| {
DatabaseError::deserialization("failed to convert to network note", source)
})?;
Ok(hint_floor(note.execution_hint())
.max(backoff_ready_block(Some(block_num), attempt_count + 1)))
}