use rusqlite::{Connection, named_params};
use zcash_protocol::consensus::BlockHeight;
#[cfg(feature = "orchard")]
use {zcash_client_backend::wallet::CompactEncryptionFields, zcash_protocol::ShieldedPool};
use crate::{TxRef, error::SqliteClientError};
pub(crate) fn suspend_orphaned_ironwood_enhancement(
conn: &rusqlite::Transaction<'_>,
) -> Result<(), SqliteClientError> {
conn.execute("UPDATE ironwood_enhance_metadata_queue AS q SET commitment_tree_position = NULL,
output_index = NULL WHERE q.compact_bound = 0 AND q.commitment_tree_position IS NOT NULL
AND NOT EXISTS (SELECT 1 FROM ironwood_received_notes rn WHERE rn.transaction_id = q.transaction_id
AND rn.action_index = q.output_index AND rn.commitment_tree_position = q.commitment_tree_position)", [])?;
conn.execute(
"UPDATE ironwood_enhance_outgoing_queue SET not_recoverable = 1
WHERE NOT EXISTS (SELECT 1 FROM ironwood_enhance_outgoing_accounts a
WHERE a.commitment_tree_position = ironwood_enhance_outgoing_queue.commitment_tree_position)",
[],
)?;
conn.execute(
"UPDATE ironwood_enhance_discovery_queue SET suspended = 1
WHERE NOT EXISTS (
SELECT 1 FROM ironwood_received_note_spends s
JOIN ironwood_received_notes rn ON rn.id = s.ironwood_received_note_id
WHERE s.transaction_id = ironwood_enhance_discovery_queue.transaction_id
AND rn.nf IS NOT NULL)",
[],
)?;
queue_ironwood_output_shape(conn, None)?;
Ok(())
}
pub(crate) fn truncate_before_unmine(
conn: &rusqlite::Transaction,
truncation_height: BlockHeight,
) -> Result<(), SqliteClientError> {
conn.execute(
"INSERT INTO ironwood_enhance_discovery_queue (transaction_id, suspended)
SELECT t.id_tx, NOT EXISTS (
SELECT 1 FROM ironwood_received_note_spends s
JOIN ironwood_received_notes rn ON rn.id = s.ironwood_received_note_id
WHERE s.transaction_id = t.id_tx AND rn.nf IS NOT NULL
)
FROM transactions t
JOIN ironwood_enhance_routing r ON r.transaction_id = t.id_tx
WHERE t.mined_height > :height AND t.raw IS NULL AND r.route = 0
AND (
EXISTS (SELECT 1 FROM ironwood_enhance_discovery_queue WHERE transaction_id = t.id_tx)
OR EXISTS (SELECT 1 FROM ironwood_enhance_outgoing_queue WHERE transaction_id = t.id_tx)
OR EXISTS (SELECT 1 FROM ironwood_received_note_spends WHERE transaction_id = t.id_tx)
)
ON CONFLICT(transaction_id) DO UPDATE SET suspended = excluded.suspended",
named_params![":height": u32::from(truncation_height)],
)?;
conn.execute(
"DELETE FROM ironwood_enhance_outgoing_queue
WHERE transaction_id IN (
SELECT id_tx FROM transactions WHERE mined_height > :height
)",
named_params![":height": u32::from(truncation_height)],
)?;
conn.execute(
"UPDATE ironwood_enhance_metadata_queue SET commitment_tree_position = NULL,
output_index = NULL, compact_bound = 0
WHERE transaction_id IN (SELECT id_tx FROM transactions WHERE mined_height > :height)",
named_params![":height": u32::from(truncation_height)],
)?;
Ok(())
}
pub(crate) fn truncate_after_unmine(conn: &rusqlite::Transaction) -> Result<(), SqliteClientError> {
conn.execute_batch(
"DELETE FROM ironwood_memo_retrieval_queue
WHERE received_note_id IN (
SELECT rn.id FROM ironwood_received_notes rn
JOIN transactions t ON t.id_tx = rn.transaction_id
WHERE t.mined_height IS NULL
)",
)?;
Ok(())
}
pub(crate) fn clear_ironwood_enhancement_work(
conn: &Connection,
tx_ref: TxRef,
) -> Result<(), SqliteClientError> {
conn.execute(
"DELETE FROM ironwood_enhance_metadata_queue WHERE transaction_id = :tx",
named_params![":tx": tx_ref.0],
)?;
conn.execute(
"DELETE FROM ironwood_enhance_discovery_queue WHERE transaction_id = :tx",
named_params![":tx": tx_ref.0],
)?;
conn.execute(
"DELETE FROM ironwood_memo_retrieval_queue
WHERE received_note_id IN (
SELECT id FROM ironwood_received_notes WHERE transaction_id = :tx)",
named_params![":tx": tx_ref.0],
)?;
conn.execute(
"DELETE FROM ironwood_enhance_outgoing_queue WHERE transaction_id = :tx",
named_params![":tx": tx_ref.0],
)?;
Ok(())
}
#[cfg(feature = "orchard")]
pub(crate) fn put_received_note_encryption_fields(
conn: &rusqlite::Transaction,
shielded_pool: ShieldedPool,
received_note_id: i64,
fields: Option<CompactEncryptionFields>,
) -> Result<(), SqliteClientError> {
if shielded_pool == ShieldedPool::Ironwood
&& let Some(fields) = fields
{
conn.execute(
"UPDATE ironwood_received_notes SET ephemeral_key = :epk,
compact_ciphertext = :ciphertext WHERE id = :id",
named_params![":epk": fields.ephemeral_key.as_slice(),
":ciphertext": fields.compact_ciphertext.as_slice(), ":id": received_note_id],
)?;
}
Ok(())
}
#[cfg(feature = "orchard")]
pub(crate) fn put_received_note_spend(
conn: &rusqlite::Transaction,
shielded_pool: ShieldedPool,
spent_in: TxRef,
inserted: usize,
) -> Result<(), SqliteClientError> {
if shielded_pool == ShieldedPool::Ironwood
&& (inserted != 0
|| conn.query_row(
"SELECT NOT EXISTS(SELECT 1 FROM ironwood_enhance_routing
WHERE transaction_id = :tx)",
named_params![":tx": spent_in.0],
|row| row.get::<_, bool>(0),
)?)
{
super::enhance_pir::discovery::queue(conn, None, spent_in)?;
}
Ok(())
}
pub(crate) fn queue_ironwood_output_shape(
conn: &rusqlite::Connection,
tx_ref: Option<crate::TxRef>,
) -> rusqlite::Result<()> {
conn.execute(
"INSERT INTO ironwood_memo_retrieval_queue (received_note_id, commitment_tree_position)
SELECT rn.id, rn.commitment_tree_position
FROM ironwood_received_notes rn
JOIN transactions t ON t.id_tx = rn.transaction_id
JOIN ironwood_enhance_routing r ON r.transaction_id = t.id_tx
WHERE r.route = 2 AND t.raw IS NULL AND t.mined_height IS NOT NULL
AND (:tx IS NULL OR t.id_tx = :tx)
AND rn.memo IS NULL AND rn.note_version = 3
AND rn.commitment_tree_position IS NOT NULL
ON CONFLICT DO NOTHING",
named_params![":tx": tx_ref.map(|tx_ref| tx_ref.0)],
)?;
conn.execute(
"INSERT INTO ironwood_enhance_metadata_queue
(transaction_id, commitment_tree_position, output_index, compact_bound)
SELECT t.id_tx, rn.commitment_tree_position, rn.action_index, 0
FROM transactions t
JOIN ironwood_enhance_routing r ON r.transaction_id = t.id_tx
JOIN ironwood_received_notes rn ON rn.transaction_id = t.id_tx
WHERE r.route = 2 AND r.has_transparent_outputs IS NULL
AND t.raw IS NULL AND t.mined_height IS NOT NULL
AND (:tx IS NULL OR t.id_tx = :tx)
AND rn.note_version = 3 AND rn.commitment_tree_position IS NOT NULL
AND rn.ephemeral_key IS NOT NULL AND rn.compact_ciphertext IS NOT NULL
AND rn.id = (SELECT MIN(n.id) FROM ironwood_received_notes n
WHERE n.transaction_id = t.id_tx AND n.note_version = 3
AND n.commitment_tree_position IS NOT NULL
AND n.ephemeral_key IS NOT NULL AND n.compact_ciphertext IS NOT NULL)
AND NOT EXISTS (SELECT 1 FROM ironwood_memo_retrieval_queue q
JOIN ironwood_received_notes n ON n.id = q.received_note_id
WHERE n.transaction_id = t.id_tx)
ON CONFLICT(transaction_id) DO UPDATE SET
commitment_tree_position = excluded.commitment_tree_position,
output_index = excluded.output_index, compact_bound = 0",
rusqlite::named_params![":tx": tx_ref.map(|tx_ref| tx_ref.0)],
)?;
Ok(())
}