#[cfg(feature = "transparent-inputs")]
use {
crate::{TxRef, UtxoId, error::SqliteClientError},
rusqlite::named_params,
transparent::bundle::OutPoint,
};
pub(crate) fn output_observation_condition(output_alias: &str) -> String {
format!(
"NOT EXISTS (
SELECT 1 FROM tpir_output_origins oo WHERE oo.output_id = {output_alias}.id AND oo.origin = 2
) OR EXISTS (
SELECT 1 FROM tpir_output_origins oo WHERE oo.output_id = {output_alias}.id AND oo.origin != 2
) OR EXISTS (
SELECT 1 FROM tpir_receive_events re
JOIN transactions rt ON rt.txid = re.txid
WHERE rt.id_tx = {output_alias}.transaction_id AND re.output_index = {output_alias}.output_index
AND re.account_id = {output_alias}.account_id AND re.mined_height IS NOT NULL
)"
)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(not(feature = "transparent-inputs"), allow(dead_code))]
pub(crate) enum ProjectionOrigin {
LegacyPublic,
LocalConstruction,
LedgerEvent,
}
impl ProjectionOrigin {
#[cfg_attr(not(feature = "transparent-inputs"), allow(dead_code))]
fn code(self) -> i64 {
match self {
Self::LegacyPublic => 0,
Self::LocalConstruction => 1,
Self::LedgerEvent => 2,
}
}
}
#[cfg(feature = "transparent-inputs")]
pub(crate) fn record_output_origin(
conn: &rusqlite::Connection,
output: UtxoId,
origin: ProjectionOrigin,
) -> Result<(), SqliteClientError> {
conn.prepare_cached(
"INSERT INTO tpir_output_origins (output_id, origin)
VALUES (:output_id, :origin)
ON CONFLICT (output_id, origin) DO NOTHING",
)?
.execute(named_params![":output_id": output.0, ":origin": origin.code()])?;
conn.prepare_cached(
"INSERT INTO tpir_output_origins (output_id, origin)
SELECT o.id, 1
FROM transparent_received_outputs o
JOIN transactions t ON t.id_tx = o.transaction_id
WHERE o.id = :output_id
AND (t.created IS NOT NULL OR t.target_height IS NOT NULL)
ON CONFLICT (output_id, origin) DO NOTHING",
)?
.execute(named_params![":output_id": output.0])?;
Ok(())
}
#[cfg(feature = "transparent-inputs")]
pub(crate) fn record_spend_origin(
conn: &rusqlite::Connection,
spent_in_tx: TxRef,
outpoint: &OutPoint,
origin: ProjectionOrigin,
) -> Result<(), SqliteClientError> {
conn.prepare_cached(
"INSERT INTO tpir_spend_origins (
spending_transaction_id, prevout_txid, prevout_output_index, origin
)
VALUES (:spent_in_tx, :prevout_txid, :prevout_idx, :origin)
ON CONFLICT (spending_transaction_id, prevout_txid, prevout_output_index, origin)
DO NOTHING",
)?
.execute(named_params![
":spent_in_tx": spent_in_tx.0,
":prevout_txid": outpoint.hash(),
":prevout_idx": outpoint.n(),
":origin": origin.code(),
])?;
conn.prepare_cached(
"INSERT INTO tpir_spend_origins (
spending_transaction_id, prevout_txid, prevout_output_index, origin
)
SELECT id_tx, :prevout_txid, :prevout_idx, 1
FROM transactions
WHERE id_tx = :spent_in_tx
AND (created IS NOT NULL OR target_height IS NOT NULL)
ON CONFLICT (spending_transaction_id, prevout_txid, prevout_output_index, origin)
DO NOTHING",
)?
.execute(named_params![
":spent_in_tx": spent_in_tx.0,
":prevout_txid": outpoint.hash(),
":prevout_idx": outpoint.n(),
])?;
Ok(())
}
#[cfg(feature = "transparent-inputs")]
pub(crate) fn record_local_origins_for_tx(
conn: &rusqlite::Connection,
txid: &[u8],
) -> Result<(), SqliteClientError> {
conn.execute(
"INSERT INTO tpir_output_origins (output_id, origin)
SELECT o.id, 1
FROM transparent_received_outputs o
JOIN transactions t ON t.id_tx = o.transaction_id
WHERE t.txid = :txid
ON CONFLICT (output_id, origin) DO NOTHING",
named_params![":txid": txid],
)?;
conn.execute(
"INSERT INTO tpir_spend_origins (
spending_transaction_id, prevout_txid, prevout_output_index, origin
)
SELECT so.spending_transaction_id, so.prevout_txid, so.prevout_output_index, 1
FROM tpir_spend_origins so
JOIN transactions t ON t.id_tx = so.spending_transaction_id
WHERE t.txid = :txid
ON CONFLICT (spending_transaction_id, prevout_txid, prevout_output_index, origin)
DO NOTHING",
named_params![":txid": txid],
)?;
Ok(())
}