From d7fa69db81a76bb59a978855c4e54e0a74773aa5 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Fri, 22 May 2026 00:55:31 +0200 Subject: [PATCH] fix(db): order swap_states by id, not entered_at `entered_at` is stored via `OffsetDateTime::Display`, whose `Time` format string does not zero-pad the hour. Timestamps with a single-digit hour (`9:43:54...`) therefore lex-sort *after* same-day two-digit-hour timestamps (`16:04:20...`), corrupting `MIN`/`MAX(entered_at)` for any swap that crosses 10:00. Switch `all_paginated` and `get_swap_start_date` to use the monotonic `id` column for ordering. `entered_at` is still returned as the display value via SQLite's bare-column-with-aggregate rule. Symptom: `get_swaps` RPC logs "Skipping swap with unexpected state history" once per poll for any Alice swap whose first persisted state landed before 10:00 UTC. --- ...3cad023c30705d5b41a1399ef79d8d2571d7c.json | 18 ----------------- ...3f2b225d397a4884178390439eefb2362dc3.json} | 4 ++-- ...f40bb1c5482d7a1af1e86fa8e578454abc811.json | 20 +++++++++++++++++++ swap/src/database/sqlite.rs | 13 ++++++------ 4 files changed, 29 insertions(+), 26 deletions(-) delete mode 100644 .sqlx/query-0d465a17ebbb5761421def759c73cad023c30705d5b41a1399ef79d8d2571d7c.json rename .sqlx/{query-8502f53826cfae8b9dbe494191ad23c693e941ddbe1d6fe0c50a47e120ac2332.json => query-e7457aff93d5f508192fe7478ca63f2b225d397a4884178390439eefb2362dc3.json} (55%) create mode 100644 .sqlx/query-fe50517ee36f9140c7591cb30d1f40bb1c5482d7a1af1e86fa8e578454abc811.json diff --git a/.sqlx/query-0d465a17ebbb5761421def759c73cad023c30705d5b41a1399ef79d8d2571d7c.json b/.sqlx/query-0d465a17ebbb5761421def759c73cad023c30705d5b41a1399ef79d8d2571d7c.json deleted file mode 100644 index e746a4c0e9..0000000000 --- a/.sqlx/query-0d465a17ebbb5761421def759c73cad023c30705d5b41a1399ef79d8d2571d7c.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT min(entered_at) as start_date\n FROM swap_states\n WHERE swap_id = ?\n ", - "describe": { - "columns": [ - { - "name": "start_date", - "ordinal": 0, - "type_info": "Text" - } - ], - "parameters": { - "Right": 1 - }, - "nullable": [true] - }, - "hash": "0d465a17ebbb5761421def759c73cad023c30705d5b41a1399ef79d8d2571d7c" -} diff --git a/.sqlx/query-8502f53826cfae8b9dbe494191ad23c693e941ddbe1d6fe0c50a47e120ac2332.json b/.sqlx/query-e7457aff93d5f508192fe7478ca63f2b225d397a4884178390439eefb2362dc3.json similarity index 55% rename from .sqlx/query-8502f53826cfae8b9dbe494191ad23c693e941ddbe1d6fe0c50a47e120ac2332.json rename to .sqlx/query-e7457aff93d5f508192fe7478ca63f2b225d397a4884178390439eefb2362dc3.json index 8daede91dd..bbf82a836d 100644 --- a/.sqlx/query-8502f53826cfae8b9dbe494191ad23c693e941ddbe1d6fe0c50a47e120ac2332.json +++ b/.sqlx/query-e7457aff93d5f508192fe7478ca63f2b225d397a4884178390439eefb2362dc3.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "\n SELECT\n peers.peer_id,\n oldest.swap_id,\n oldest.state AS \"first_state!: String\",\n newest.state AS \"last_state!: String\",\n oldest.entered_at AS start_date\n FROM (SELECT swap_id, state, MIN(entered_at) AS entered_at\n FROM swap_states GROUP BY swap_id) oldest\n JOIN (SELECT swap_id, state, MAX(entered_at) AS entered_at\n FROM swap_states GROUP BY swap_id) newest\n ON newest.swap_id = oldest.swap_id\n JOIN peers ON peers.swap_id = oldest.swap_id\n WHERE NOT (\n json_extract(oldest.state, '$.Alice.Done') IS 'SafelyAborted'\n AND json_extract(newest.state, '$.Alice.Done') IS 'SafelyAborted'\n )\n ORDER BY oldest.entered_at\n LIMIT ?\n OFFSET ?\n ", + "query": "\n SELECT\n peers.peer_id,\n oldest.swap_id,\n oldest.state AS \"first_state!: String\",\n newest.state AS \"last_state!: String\",\n oldest.entered_at AS start_date\n FROM (SELECT swap_id, state, entered_at, MIN(id) AS id\n FROM swap_states GROUP BY swap_id) oldest\n JOIN (SELECT swap_id, state, MAX(id) AS id\n FROM swap_states GROUP BY swap_id) newest\n ON newest.swap_id = oldest.swap_id\n JOIN peers ON peers.swap_id = oldest.swap_id\n WHERE NOT (\n json_extract(oldest.state, '$.Alice.Done') IS 'SafelyAborted'\n AND json_extract(newest.state, '$.Alice.Done') IS 'SafelyAborted'\n )\n ORDER BY oldest.id\n LIMIT ?\n OFFSET ?\n ", "describe": { "columns": [ { @@ -40,5 +40,5 @@ false ] }, - "hash": "8502f53826cfae8b9dbe494191ad23c693e941ddbe1d6fe0c50a47e120ac2332" + "hash": "e7457aff93d5f508192fe7478ca63f2b225d397a4884178390439eefb2362dc3" } diff --git a/.sqlx/query-fe50517ee36f9140c7591cb30d1f40bb1c5482d7a1af1e86fa8e578454abc811.json b/.sqlx/query-fe50517ee36f9140c7591cb30d1f40bb1c5482d7a1af1e86fa8e578454abc811.json new file mode 100644 index 0000000000..544640b7c9 --- /dev/null +++ b/.sqlx/query-fe50517ee36f9140c7591cb30d1f40bb1c5482d7a1af1e86fa8e578454abc811.json @@ -0,0 +1,20 @@ +{ + "db_name": "SQLite", + "query": "\n SELECT entered_at as start_date\n FROM swap_states\n WHERE swap_id = ?\n ORDER BY id ASC\n LIMIT 1\n ", + "describe": { + "columns": [ + { + "name": "start_date", + "ordinal": 0, + "type_info": "Text" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false + ] + }, + "hash": "fe50517ee36f9140c7591cb30d1f40bb1c5482d7a1af1e86fa8e578454abc811" +} diff --git a/swap/src/database/sqlite.rs b/swap/src/database/sqlite.rs index 42b894187c..389d906edc 100644 --- a/swap/src/database/sqlite.rs +++ b/swap/src/database/sqlite.rs @@ -285,17 +285,18 @@ impl Database for SqliteDatabase { let row = sqlx::query!( r#" - SELECT min(entered_at) as start_date + SELECT entered_at as start_date FROM swap_states WHERE swap_id = ? + ORDER BY id ASC + LIMIT 1 "#, swap_id ) .fetch_one(&self.pool) .await?; - row.start_date - .ok_or_else(|| anyhow!("Could not get swap start date")) + Ok(row.start_date) } async fn insert_latest_state(&self, swap_id: Uuid, state: State) -> Result<()> { @@ -420,9 +421,9 @@ impl Database for SqliteDatabase { oldest.state AS "first_state!: String", newest.state AS "last_state!: String", oldest.entered_at AS start_date - FROM (SELECT swap_id, state, MIN(entered_at) AS entered_at + FROM (SELECT swap_id, state, entered_at, MIN(id) AS id FROM swap_states GROUP BY swap_id) oldest - JOIN (SELECT swap_id, state, MAX(entered_at) AS entered_at + JOIN (SELECT swap_id, state, MAX(id) AS id FROM swap_states GROUP BY swap_id) newest ON newest.swap_id = oldest.swap_id JOIN peers ON peers.swap_id = oldest.swap_id @@ -430,7 +431,7 @@ impl Database for SqliteDatabase { json_extract(oldest.state, '$.Alice.Done') IS 'SafelyAborted' AND json_extract(newest.state, '$.Alice.Done') IS 'SafelyAborted' ) - ORDER BY oldest.entered_at + ORDER BY oldest.id LIMIT ? OFFSET ? "#,