use rusqlite::{Connection, params};
use zakura_one_time_ivk::swap::{REFUND_MEMO_MAGIC, RefundMemo};
use zcash_protocol::consensus::{BlockHeight, Parameters};
use crate::wallet::one_time_ivk::{KeyId, Purpose, decode_index, register};
use crate::{AccountUuid, error::SqliteClientError, wallet};
pub(in crate::wallet::one_time_ivk) fn refund_memos_pending(
conn: &Connection,
account: AccountUuid,
) -> Result<bool, SqliteClientError> {
Ok(conn.query_row(
"SELECT EXISTS(SELECT 1 FROM ironwood_received_notes n
JOIN transactions t ON t.id_tx = n.transaction_id
JOIN accounts a ON a.id = n.account_id
WHERE a.uuid = ?1 AND n.recipient_key_scope = 1
AND n.one_time_key_id IS NULL AND t.mined_height IS NOT NULL
AND n.memo IS NULL)",
[account.0],
|r| r.get(0),
)?)
}
const REFUND_MEMO_GAP: u64 = 100;
pub(in crate::wallet::one_time_ivk) fn recover_refund_memos<P: Parameters>(
conn: &rusqlite::Transaction<'_>,
params: &P,
account: AccountUuid,
) -> Result<(), SqliteClientError> {
let owner = wallet::get_account_ref(conn, account)?;
let records = conn
.prepare_cached(
"SELECT n.memo, t.mined_height,
EXISTS(SELECT 1 FROM v_received_output_spends s
WHERE s.transaction_id = n.transaction_id AND s.account_id = n.account_id)
FROM ironwood_received_notes n
JOIN transactions t ON t.id_tx = n.transaction_id
WHERE n.account_id = ?1 AND n.recipient_key_scope = 1
AND n.one_time_key_id IS NULL AND t.mined_height IS NOT NULL
AND substr(n.memo, 1, 5) = ?2",
)?
.query_map(params![owner.0, REFUND_MEMO_MAGIC], |r| {
Ok((
r.get::<_, Vec<u8>>(0)?,
r.get::<_, u32>(1)?,
r.get::<_, bool>(2)?,
))
})?
.collect::<Result<Vec<_>, _>>()?;
let mut memos: Vec<(u32, u64, bool)> = records
.into_iter()
.filter_map(|(bytes, height, own_send)| {
zcash_protocol::memo::MemoBytes::from_bytes(&bytes)
.ok()
.and_then(|bytes| RefundMemo::decode(bytes.as_array()))
.map(|memo| (height, memo.index(), own_send))
})
.collect();
memos.sort_unstable();
let highest: Option<Vec<u8>> = conn.query_row(
"SELECT MAX(key_index) FROM ironwood_one_time_keys WHERE account_id = ?1 AND purpose = 0",
[owner.0],
|r| r.get(0),
)?;
let mut highest = highest.map(decode_index).transpose()?.unwrap_or(0);
loop {
let mut deferred = vec![];
for (height, index, own_send) in memos {
if !own_send && index > highest.saturating_add(REFUND_MEMO_GAP) {
deferred.push((height, index, own_send));
continue;
}
let (id, _) = register(conn, params, account, KeyId::new(Purpose::Refund, index))?;
super::queue::funded(conn, params, id, BlockHeight::from(height))?;
highest = highest.max(index);
}
if deferred.is_empty()
|| deferred
.iter()
.all(|(_, i, _)| *i > highest.saturating_add(REFUND_MEMO_GAP))
{
return Ok(());
}
memos = deferred;
}
}