From f603baf2efc870568b4321e4882822c22b9b8a28 Mon Sep 17 00:00:00 2001 From: Jakob Notland Date: Tue, 31 Mar 2026 12:08:04 +0200 Subject: [PATCH] Maybe migrate db --- src/db/cauldron/ido.rs | 41 +++++++++++++++++++++++++++++++++++------ src/db/init.rs | 4 ++++ 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/db/cauldron/ido.rs b/src/db/cauldron/ido.rs index e32bc4a..ff756ac 100644 --- a/src/db/cauldron/ido.rs +++ b/src/db/cauldron/ido.rs @@ -88,9 +88,38 @@ pub fn deserialize_ido_params(data: &[u8]) -> Option { }) } +/// Migrate the IDO schema from v1 (category, no preinit/entry tables) to v2. +/// Safe to call on a fresh DB (no-op if v2 tables already exist) or an old DB +/// (drops and recreates the IDO tables; safe because no IDOs are indexed yet). +async fn maybe_migrate(pool: &SqlitePool) { + // If ido_preinit_utxo doesn't exist we're on v1 (or fresh). Drop everything + // IDO-related and let create_tables build the v2 schema from scratch. + let needs_migration: bool = sqlx::query_scalar( + "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='ido_preinit_utxo'", + ) + .fetch_one(pool) + .await + .map(|n: i64| n == 0) + .unwrap_or(true); + + if !needs_migration { + return; + } + + // Drop in dependency order (FK constraints) + for tbl in &["ido_entry", "ido_preinit_utxo", "ido_state", "ido"] { + sqlx::query(&format!("DROP TABLE IF EXISTS {tbl}")) + .execute(pool) + .await + .unwrap_or_else(|e| panic!("failed to drop {tbl}: {e}")); + } +} + pub async fn create_tables(pool: &SqlitePool) { + maybe_migrate(pool).await; + sqlx::query( - "CREATE TABLE ido ( + "CREATE TABLE IF NOT EXISTS ido ( creation_txid BLOB PRIMARY KEY, -- Token being sold (from output[2] of publishIdoPreInit) offered_token_category BLOB NOT NULL, @@ -106,14 +135,14 @@ pub async fn create_tables(pool: &SqlitePool) { .expect("failed to create ido table"); sqlx::query( - "CREATE INDEX idx_ido_offered_token_category ON ido(offered_token_category)", + "CREATE INDEX IF NOT EXISTS idx_ido_offered_token_category ON ido(offered_token_category)", ) .execute(pool) .await .expect("failed to create idx_ido_offered_token_category"); sqlx::query( - "CREATE TABLE ido_state ( + "CREATE TABLE IF NOT EXISTS ido_state ( creation_txid BLOB PRIMARY KEY REFERENCES ido(creation_txid) ON DELETE CASCADE, distributor_utxo_txid BLOB, distributor_utxo_n INT, @@ -127,7 +156,7 @@ pub async fn create_tables(pool: &SqlitePool) { // Tracks the moving pre-init contract UTXO (output[1]) through idoPreInitStep txs. // When createIDO fires, this row is deleted and offering_category is set on the ido row. sqlx::query( - "CREATE TABLE ido_preinit_utxo ( + "CREATE TABLE IF NOT EXISTS ido_preinit_utxo ( ido_creation_txid BLOB NOT NULL REFERENCES ido(creation_txid) ON DELETE CASCADE, utxo_txid BLOB NOT NULL, utxo_n INTEGER NOT NULL, @@ -141,7 +170,7 @@ pub async fn create_tables(pool: &SqlitePool) { // Unspent entry NFT UTXOs created by participants. // Populated once offering_category is known (after createIDO fires). sqlx::query( - "CREATE TABLE ido_entry ( + "CREATE TABLE IF NOT EXISTS ido_entry ( utxo_txid BLOB NOT NULL, utxo_n INTEGER NOT NULL, offering_category BLOB NOT NULL, @@ -156,7 +185,7 @@ pub async fn create_tables(pool: &SqlitePool) { .expect("failed to create ido_entry table"); sqlx::query( - "CREATE INDEX idx_ido_entry_offering ON ido_entry(offering_category, spent)", + "CREATE INDEX IF NOT EXISTS idx_ido_entry_offering ON ido_entry(offering_category, spent)", ) .execute(pool) .await diff --git a/src/db/init.rs b/src/db/init.rs index 855294e..63bedf4 100644 --- a/src/db/init.rs +++ b/src/db/init.rs @@ -13,6 +13,7 @@ use std::time::Duration; use super::DB; use crate::db::bcmr::prepare_tables as bcmr_prepare_tables; use crate::db::cauldron::config::check_db_version; +use crate::db::cauldron::ido; use crate::db::cauldron::prepare_tables as cauldron_prepare_tables; use crate::db::crc20::prepare_tables as crc20_prepare_tables; use crate::db::oracle::prepare_tables as oracle_prepare_tables; @@ -100,6 +101,9 @@ pub async fn initialize_databases(network: &str, read_slots: ReadSlots) -> Resul cauldron_prepare_tables(&cauldron_db_write).await; } else { check_db_version(&cauldron_db_read).await?; + // Run IDO table migration/creation separately so that schema additions + // take effect on existing databases without requiring a full DB wipe. + ido::create_tables(&cauldron_db_write).await; } // Initialize BCMR database