From 2acbe348d2aa4917dda66a36734e37efdb1b1e09 Mon Sep 17 00:00:00 2001 From: valued mammal Date: Tue, 30 Dec 2025 02:47:18 -0500 Subject: [PATCH 1/2] schema: Add migration `0002_schema.up.sql` `0002_schema.up.sql` changes the PRIMARY KEY of `block` table from `(height, hash)` to `height` only, in order to prevent multiple rows from contending for the same height. **NOTE** this operation is potentially destructive. If two or more rows existed at the same height, they'll now be missing from the database since we can't reliably determine which block hash is the correct one. Additionally, the anchor `block_hash` column previously had the type INTEGER, and this was a mistake in the schema, since block hashes are typically represented as a hex string. Thus we change the type of the `block_hash` column to TEXT. --- migrations/0002_schema.up.sql | 46 +++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 migrations/0002_schema.up.sql diff --git a/migrations/0002_schema.up.sql b/migrations/0002_schema.up.sql new file mode 100644 index 0000000..cf279f8 --- /dev/null +++ b/migrations/0002_schema.up.sql @@ -0,0 +1,46 @@ +-- 0002_schema_up.sql + +-- ******************************************** -- +-- Change primary key of block table to height. -- +-- ******************************************** -- + +-- Create new table +CREATE TABLE IF NOT EXISTS block_new( + height INTEGER PRIMARY KEY NOT NULL, + hash TEXT NOT NULL +); +-- Copy only unique rows +INSERT INTO block_new(height, hash) +SELECT height, hash +FROM block +WHERE height IN( + SELECT height + FROM block + GROUP BY height + HAVING COUNT(*) = 1 +); +-- Drop old table +DROP TABLE block; +-- Rename new table to old +ALTER TABLE block_new RENAME TO block; + +-- ************************************************ -- +-- Change type of anchor block_hash column to TEXT. -- +-- ************************************************ -- + +-- Create new table +CREATE TABLE IF NOT EXISTS anchor_new( + block_height INTEGER NOT NULL, + block_hash TEXT NOT NULL, + txid TEXT NOT NULL, + confirmation_time INTEGER NOT NULL, + PRIMARY KEY(block_height, block_hash, txid) +); +-- Copy old data +INSERT INTO anchor_new(block_height, block_hash, txid, confirmation_time) +SELECT block_height, block_hash, txid, confirmation_time +FROM anchor; +-- Delete old table +DROP TABLE anchor; +-- Rename new table to old table +ALTER TABLE anchor_new RENAME TO anchor; From 4d5747886a8c8be894a57d2d922f23a67a875463 Mon Sep 17 00:00:00 2001 From: valued mammal Date: Tue, 30 Dec 2025 03:26:42 -0500 Subject: [PATCH 2/2] refactor: Simplify query in `write_local_chain` Now that the schema has a unique constraint on block height, we can remove the extra select query. --- src/async_store.rs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/async_store.rs b/src/async_store.rs index 12a661e..dd1e5fa 100644 --- a/src/async_store.rs +++ b/src/async_store.rs @@ -140,20 +140,11 @@ impl Store { for (&height, hash) in &local_chain.blocks { match hash { Some(hash) => { - // Avoid inserting new rows of existing height. - // FIXME: The correct way to handle this is to have a unique constraint on `height` - // in the block table schema. - let row_option = sqlx::query("SELECT height FROM block WHERE height = $1") + sqlx::query("INSERT OR IGNORE INTO block(height, hash) VALUES($1, $2)") .bind(height) - .fetch_optional(&self.pool) + .bind(hash.to_string()) + .execute(&self.pool) .await?; - if row_option.is_none() { - sqlx::query("INSERT OR IGNORE INTO block(height, hash) VALUES($1, $2)") - .bind(height) - .bind(hash.to_string()) - .execute(&self.pool) - .await?; - } } None => { sqlx::query("DELETE FROM block WHERE height = $1")