use super::ironwood_transparent_output_shape;
use crate::wallet::init::WalletMigrationError;
use schemerz_rusqlite::RusqliteMigration;
use std::collections::HashSet;
use uuid::Uuid;
pub const MIGRATION_ID: Uuid = Uuid::from_u128(0x73d751a3_dbdc_461a_9154_e061903aae4f);
pub(super) struct Migration;
impl schemerz::Migration<Uuid> for Migration {
fn id(&self) -> Uuid {
MIGRATION_ID
}
fn dependencies(&self) -> HashSet<Uuid> {
[ironwood_transparent_output_shape::MIGRATION_ID]
.into_iter()
.collect()
}
fn description(&self) -> &'static str {
"Adds transparent txid enhancement work and display facts."
}
}
impl RusqliteMigration for Migration {
type Error = WalletMigrationError;
fn up(&self, conn: &rusqlite::Transaction) -> Result<(), Self::Error> {
conn.execute_batch(
r#"
CREATE TABLE transparent_detail_work (
transaction_id INTEGER PRIMARY KEY REFERENCES transactions(id_tx) ON DELETE CASCADE,
reasons INTEGER NOT NULL CHECK (reasons > 0 AND reasons < 8),
attempts INTEGER NOT NULL DEFAULT 0 CHECK (attempts >= 0),
next_attempt_at INTEGER NOT NULL DEFAULT 0,
attempted_at INTEGER,
attempted_height INTEGER CHECK (attempted_height >= 0),
last_outcome INTEGER CHECK (last_outcome BETWEEN 0 AND 6),
last_map_sha256 BLOB CHECK (length(last_map_sha256) = 32)
);
CREATE TABLE transparent_tx_display (
transaction_id INTEGER PRIMARY KEY REFERENCES transactions(id_tx) ON DELETE CASCADE,
coinbase INTEGER NOT NULL CHECK (coinbase IN (0, 1)),
fee_zat INTEGER NOT NULL CHECK (fee_zat >= 0 AND fee_zat <= 2100000000000000),
input_count INTEGER NOT NULL CHECK (input_count >= 0 AND input_count <= 4294967295),
output_count INTEGER NOT NULL CHECK (output_count >= 0 AND output_count <= 4294967295),
shielded INTEGER NOT NULL CHECK (shielded IN (0, 1)),
sender_kind INTEGER NOT NULL CHECK (sender_kind IN (0, 1, 2, 3)),
sender_hash BLOB CHECK (length(sender_hash) = 20),
multiple_source_scripts INTEGER NOT NULL CHECK (multiple_source_scripts IN (0, 1)),
shielded_and_transparent_funding INTEGER NOT NULL
CHECK (shielded_and_transparent_funding IN (0, 1)),
shard_id INTEGER NOT NULL,
revision INTEGER NOT NULL CHECK (revision >= 0),
map_sha256 BLOB NOT NULL CHECK (length(map_sha256) = 32),
looked_up_height INTEGER NOT NULL CHECK (looked_up_height >= 0),
stored_at INTEGER NOT NULL,
CHECK ((sender_kind IN (1, 2)) = (sender_hash IS NOT NULL)),
CHECK ((sender_kind = 0) = (input_count = 0)),
CHECK (coinbase = 0 OR (fee_zat = 0 AND input_count = 0)),
CHECK (coinbase = 1 OR input_count > 0 OR shielded = 1),
CHECK (multiple_source_scripts = 0 OR input_count >= 2),
CHECK (shielded_and_transparent_funding = 0 OR (input_count > 0 AND shielded = 1))
);
CREATE TABLE transparent_tx_display_outputs (
transaction_id INTEGER NOT NULL
REFERENCES transparent_tx_display(transaction_id) ON DELETE CASCADE,
output_index INTEGER NOT NULL CHECK (output_index IN (0, 1)),
value_zat INTEGER NOT NULL CHECK (value_zat >= 0 AND value_zat <= 2100000000000000),
address_kind INTEGER NOT NULL CHECK (address_kind IN (1, 2, 3)),
address_hash BLOB CHECK (length(address_hash) = 20),
CHECK ((address_kind = 3) = (address_hash IS NULL)),
PRIMARY KEY (transaction_id, output_index)
);
-- Route 2 (private details unsupported) and ledger-origin (2) outputs and spends, of
-- mined transactions without raw bytes. A ledger-only output whose receive was
-- withdrawn (no placed receive event) is not the wallet's.
INSERT INTO transparent_detail_work (transaction_id, reasons)
SELECT id_tx, reasons FROM (
SELECT t.id_tx,
(CASE WHEN EXISTS (
SELECT 1 FROM transparent_received_outputs o
JOIN tpir_output_origins oo ON oo.output_id = o.id
WHERE o.transaction_id = t.id_tx AND oo.origin = 2
AND (
EXISTS (SELECT 1 FROM tpir_output_origins x
WHERE x.output_id = o.id AND x.origin != 2)
OR EXISTS (SELECT 1 FROM tpir_receive_events re
WHERE re.txid = t.txid
AND re.output_index = o.output_index
AND re.account_id = o.account_id
AND re.mined_height IS NOT NULL)
)) THEN 1 ELSE 0 END)
| (CASE WHEN EXISTS (
SELECT 1 FROM tpir_spend_origins so
WHERE so.spending_transaction_id = t.id_tx AND so.origin = 2)
THEN 2 ELSE 0 END)
| (CASE WHEN EXISTS (
SELECT 1 FROM ironwood_enhance_routing r
WHERE r.transaction_id = t.id_tx AND r.route = 2) THEN 4 ELSE 0 END)
AS reasons
FROM transactions t
WHERE t.raw IS NULL AND t.mined_height IS NOT NULL
)
WHERE reasons > 0;
"#,
)?;
Ok(())
}
fn down(&self, _conn: &rusqlite::Transaction) -> Result<(), Self::Error> {
Err(WalletMigrationError::CannotRevert(MIGRATION_ID))
}
}
#[cfg(test)]
mod tests {
#[test]
fn migrate() {
super::super::tests::test_migrate(&[super::MIGRATION_ID]);
}
}