diff --git a/.sqlx/query-10e105b51b9a1c4a9a92bb85b0d1c1ea21f7e636299e33d295c4d08bcc78cdac.json b/.sqlx/query-10e105b51b9a1c4a9a92bb85b0d1c1ea21f7e636299e33d295c4d08bcc78cdac.json new file mode 100644 index 00000000..29351fad --- /dev/null +++ b/.sqlx/query-10e105b51b9a1c4a9a92bb85b0d1c1ea21f7e636299e33d295c4d08bcc78cdac.json @@ -0,0 +1,20 @@ +{ + "db_name": "SQLite", + "query": "SELECT `collection_id` FROM `collections`", + "describe": { + "columns": [ + { + "name": "collection_id", + "ordinal": 0, + "type_info": "Blob" + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + false + ] + }, + "hash": "10e105b51b9a1c4a9a92bb85b0d1c1ea21f7e636299e33d295c4d08bcc78cdac" +} diff --git a/.sqlx/query-24ea4bd05a6542302f7baa5a41b78acde5237581748cdea39893cc4ae99b1998.json b/.sqlx/query-24ea4bd05a6542302f7baa5a41b78acde5237581748cdea39893cc4ae99b1998.json new file mode 100644 index 00000000..318072cb --- /dev/null +++ b/.sqlx/query-24ea4bd05a6542302f7baa5a41b78acde5237581748cdea39893cc4ae99b1998.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n UPDATE `collections` SET\n `collection_id` = ?,\n `did_id` = ?,\n `metadata_collection_id` = ?,\n `visible` = ?,\n `name` = ?,\n `icon` = ?\n WHERE `collection_id` = ?\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 7 + }, + "nullable": [] + }, + "hash": "24ea4bd05a6542302f7baa5a41b78acde5237581748cdea39893cc4ae99b1998" +} diff --git a/.sqlx/query-30ebf1aa19c50530c019d217be1e5647fc77980c79dce94edc1aa5771b5b725f.json b/.sqlx/query-30ebf1aa19c50530c019d217be1e5647fc77980c79dce94edc1aa5771b5b725f.json new file mode 100644 index 00000000..e115f25a --- /dev/null +++ b/.sqlx/query-30ebf1aa19c50530c019d217be1e5647fc77980c79dce94edc1aa5771b5b725f.json @@ -0,0 +1,20 @@ +{ + "db_name": "SQLite", + "query": "SELECT `version` FROM `rust_migrations` LIMIT 1", + "describe": { + "columns": [ + { + "name": "version", + "ordinal": 0, + "type_info": "Integer" + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + false + ] + }, + "hash": "30ebf1aa19c50530c019d217be1e5647fc77980c79dce94edc1aa5771b5b725f" +} diff --git a/.sqlx/query-808635aff0ab3dd13109fec8dbac511d2f781525bfa22dd83617e512e0c0efbb.json b/.sqlx/query-808635aff0ab3dd13109fec8dbac511d2f781525bfa22dd83617e512e0c0efbb.json new file mode 100644 index 00000000..8444fc24 --- /dev/null +++ b/.sqlx/query-808635aff0ab3dd13109fec8dbac511d2f781525bfa22dd83617e512e0c0efbb.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "UPDATE `rust_migrations` SET `version` = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + }, + "hash": "808635aff0ab3dd13109fec8dbac511d2f781525bfa22dd83617e512e0c0efbb" +} diff --git a/.sqlx/query-cbe56b7b03c2cd4c662a183791936b4141b4eab5ab0078649d931c6d1344a303.json b/.sqlx/query-cbe56b7b03c2cd4c662a183791936b4141b4eab5ab0078649d931c6d1344a303.json deleted file mode 100644 index 6bf50fea..00000000 --- a/.sqlx/query-cbe56b7b03c2cd4c662a183791936b4141b4eab5ab0078649d931c6d1344a303.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n REPLACE INTO `collections` (\n `collection_id`,\n `did_id`,\n `metadata_collection_id`,\n `visible`,\n `name`,\n `icon`\n )\n VALUES (?, ?, ?, ?, ?, ?)\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 6 - }, - "nullable": [] - }, - "hash": "cbe56b7b03c2cd4c662a183791936b4141b4eab5ab0078649d931c6d1344a303" -} diff --git a/Cargo.lock b/Cargo.lock index 4bbdf72a..9e481158 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4964,8 +4964,10 @@ version = "0.9.3" dependencies = [ "chia", "chia-wallet-sdk", + "hex", "sqlx", "thiserror 1.0.69", + "tracing", ] [[package]] diff --git a/crates/sage-database/Cargo.toml b/crates/sage-database/Cargo.toml index 857cbc1b..034cafeb 100644 --- a/crates/sage-database/Cargo.toml +++ b/crates/sage-database/Cargo.toml @@ -19,3 +19,5 @@ chia = { workspace = true } chia-wallet-sdk = { workspace = true } sqlx = { workspace = true, features = ["sqlite"] } thiserror = { workspace = true } +hex = { workspace = true } +tracing = { workspace = true } diff --git a/crates/sage-database/src/lib.rs b/crates/sage-database/src/lib.rs index 72609543..82d51032 100644 --- a/crates/sage-database/src/lib.rs +++ b/crates/sage-database/src/lib.rs @@ -9,6 +9,7 @@ mod utils; pub use primitives::*; pub use rows::*; +use tracing::info; pub use transactions::*; pub(crate) use utils::*; @@ -32,6 +33,45 @@ impl Database { let tx = self.pool.begin().await?; Ok(DatabaseTx::new(tx)) } + + pub async fn run_rust_migrations(&self) -> Result<()> { + let mut tx = self.tx().await?; + + let version = tx.rust_migration_version().await?; + + info!("The current Sage migration version is {version}"); + + if version == 0 { + info!("Migrating to version 1 (fixed collection id calculation)"); + + for collection_id in tx.collection_ids().await? { + let collection = tx + .collection(collection_id) + .await? + .expect("collection not found"); + + tx.update_collection( + collection_id, + CollectionRow { + collection_id: calculate_collection_id( + collection.did_id, + &collection.metadata_collection_id, + ), + did_id: collection.did_id, + metadata_collection_id: collection.metadata_collection_id, + visible: collection.visible, + name: collection.name, + icon: collection.icon, + }, + ) + .await?; + } + + tx.set_rust_migration_version(1).await?; + } + + Ok(()) + } } #[derive(Debug)] @@ -51,6 +91,22 @@ impl<'a> DatabaseTx<'a> { pub async fn rollback(self) -> Result<()> { Ok(self.tx.rollback().await?) } + + pub async fn rust_migration_version(&mut self) -> Result { + let row = sqlx::query_scalar!("SELECT `version` FROM `rust_migrations` LIMIT 1") + .fetch_one(&mut *self.tx) + .await?; + + Ok(row) + } + + pub async fn set_rust_migration_version(&mut self, version: i64) -> Result<()> { + sqlx::query!("UPDATE `rust_migrations` SET `version` = ?", version) + .execute(&mut *self.tx) + .await?; + + Ok(()) + } } #[derive(Debug, Error)] diff --git a/crates/sage-database/src/primitives/nfts.rs b/crates/sage-database/src/primitives/nfts.rs index f07308f9..ab739ee7 100644 --- a/crates/sage-database/src/primitives/nfts.rs +++ b/crates/sage-database/src/primitives/nfts.rs @@ -1,6 +1,7 @@ use chia::{ protocol::{Bytes32, Program}, puzzles::LineageProof, + sha2::Sha256, }; use chia_wallet_sdk::{Nft, NftInfo}; use sqlx::SqliteExecutor; @@ -47,6 +48,13 @@ pub struct NftSearchParams { pub name: Option, } +pub fn calculate_collection_id(did_id: Bytes32, json_collection_id: &str) -> Bytes32 { + let mut hasher = Sha256::new(); + hasher.update(hex::encode(did_id)); + hasher.update(json_collection_id); + hasher.finalize().into() +} + impl Database { pub async fn unchecked_nft_uris(&self, limit: u32) -> Result> { unchecked_nft_uris(&self.pool, limit).await @@ -232,8 +240,12 @@ impl<'a> DatabaseTx<'a> { insert_collection(&mut *self.tx, row).await } - pub async fn update_collection(&mut self, row: CollectionRow) -> Result<()> { - update_collection(&mut *self.tx, row).await + pub async fn update_collection( + &mut self, + collection_id: Bytes32, + row: CollectionRow, + ) -> Result<()> { + update_collection(&mut *self.tx, collection_id, row).await } pub async fn set_nft_not_owned(&mut self, coin_id: Bytes32) -> Result<()> { @@ -247,6 +259,10 @@ impl<'a> DatabaseTx<'a> { ) -> Result<()> { set_nft_created_height(&mut *self.tx, coin_id, height).await } + + pub async fn collection_ids(&mut self) -> Result> { + collection_ids(&mut *self.tx).await + } } async fn insert_collection(conn: impl SqliteExecutor<'_>, row: CollectionRow) -> Result<()> { @@ -280,30 +296,35 @@ async fn insert_collection(conn: impl SqliteExecutor<'_>, row: CollectionRow) -> Ok(()) } -async fn update_collection(conn: impl SqliteExecutor<'_>, row: CollectionRow) -> Result<()> { - let collection_id = row.collection_id.as_ref(); +async fn update_collection( + conn: impl SqliteExecutor<'_>, + collection_id: Bytes32, + row: CollectionRow, +) -> Result<()> { + let collection_id = collection_id.as_ref(); + let new_collection_id = row.collection_id.as_ref(); let did_id = row.did_id.as_ref(); let name = row.name.as_deref(); let icon = row.icon.as_deref(); sqlx::query!( " - REPLACE INTO `collections` ( - `collection_id`, - `did_id`, - `metadata_collection_id`, - `visible`, - `name`, - `icon` - ) - VALUES (?, ?, ?, ?, ?, ?) + UPDATE `collections` SET + `collection_id` = ?, + `did_id` = ?, + `metadata_collection_id` = ?, + `visible` = ?, + `name` = ?, + `icon` = ? + WHERE `collection_id` = ? ", - collection_id, + new_collection_id, did_id, row.metadata_collection_id, row.visible, name, - icon + icon, + collection_id ) .execute(conn) .await?; @@ -1114,3 +1135,12 @@ async fn set_nft_created_height( Ok(()) } + +async fn collection_ids(conn: impl SqliteExecutor<'_>) -> Result> { + sqlx::query_scalar!("SELECT `collection_id` FROM `collections`") + .fetch_all(conn) + .await? + .into_iter() + .map(|row| to_bytes32(&row)) + .collect() +} diff --git a/crates/sage-wallet/src/utils/offchain_metadata.rs b/crates/sage-wallet/src/utils/offchain_metadata.rs index 548ca03a..14f94b4c 100644 --- a/crates/sage-wallet/src/utils/offchain_metadata.rs +++ b/crates/sage-wallet/src/utils/offchain_metadata.rs @@ -1,6 +1,6 @@ -use chia::{protocol::Bytes32, sha2::Sha256}; +use chia::protocol::Bytes32; use sage_assets::{Chip0007Metadata, Collection}; -use sage_database::CollectionRow; +use sage_database::{calculate_collection_id, CollectionRow}; #[derive(Debug, Default, Clone)] pub struct ComputedNftInfo { @@ -49,13 +49,6 @@ pub fn compute_nft_info(did_id: Option, blob: Option<&[u8]>) -> Compute } } -fn calculate_collection_id(did_id: Bytes32, json_collection_id: &str) -> Bytes32 { - let mut hasher = Sha256::new(); - hasher.update(hex::encode(did_id)); - hasher.update(json_collection_id); - hasher.finalize().into() -} - #[cfg(test)] mod tests { use hex_literal::hex; diff --git a/migrations/0009_rust_migrations.sql b/migrations/0009_rust_migrations.sql new file mode 100644 index 00000000..357c4a9f --- /dev/null +++ b/migrations/0009_rust_migrations.sql @@ -0,0 +1,5 @@ +CREATE TABLE `rust_migrations` ( + `version` INTEGER PRIMARY KEY +); + +INSERT INTO `rust_migrations` (`version`) VALUES (0);