From 023090c9de0b24c960f86d98d0f7b74aff3b66a0 Mon Sep 17 00:00:00 2001 From: michael1011 Date: Sun, 30 Jun 2024 15:27:19 +0200 Subject: [PATCH] feat: PostgreSQL support --- .env | 3 ++ Cargo.lock | 21 ++++++++- Cargo.toml | 4 +- .../2024-02-14-014006_setup_schema/down.sql | 2 + .../2024-02-14-014006_setup_schema/up.sql | 19 ++++++++ src/db/helpers.rs | 36 +++++++++++--- src/db/mod.rs | 47 +++++++++++++++---- src/main.rs | 18 +++++-- 8 files changed, 125 insertions(+), 25 deletions(-) create mode 100644 migrations_postgres/2024-02-14-014006_setup_schema/down.sql create mode 100644 migrations_postgres/2024-02-14-014006_setup_schema/up.sql diff --git a/.env b/.env index b495aa4..dbbe209 100644 --- a/.env +++ b/.env @@ -1,6 +1,9 @@ RUST_LOG=trace,hyper=info,tracing=info,reqwest=info # The database that should be used +# SQLite and PostgreSQL are supported: +# - sqlite://./db.sqlite +# - postgresql://boltz:boltz@127.0.0.1:5432/covclaim DATABASE_URL=sqlite://./db.sqlite # When finding a lockup transaction, how many seconds to wait before broadcasting the covenant claim (0 for instantly) diff --git a/Cargo.lock b/Cargo.lock index db514c7..88ce4da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -276,6 +276,12 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.6.0" @@ -347,7 +353,7 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "covclaim" -version = "0.1.0" +version = "0.2.0" dependencies = [ "async-trait", "axum", @@ -481,9 +487,13 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62d6dcd069e7b5fe49a302411f759d4cf1cf2c27fe798ef46fb8baefc053dd2b" dependencies = [ + "bitflags 2.6.0", + "byteorder", "chrono", "diesel_derives", + "itoa", "libsqlite3-sys", + "pq-sys", "r2d2", "time", ] @@ -1342,6 +1352,15 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "pq-sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a24ff9e4cf6945c988f0db7005d87747bf72864965c3529d259ad155ac41d584" +dependencies = [ + "vcpkg", +] + [[package]] name = "proc-macro2" version = "1.0.86" diff --git a/Cargo.toml b/Cargo.toml index 090f657..c009415 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "covclaim" -version = "0.1.0" +version = "0.2.0" edition = "2021" build = "build.rs" @@ -14,7 +14,7 @@ panic = "abort" [dependencies] tokio = { version = "1.38.0", features = ["full"] } axum = "0.7.5" -diesel = { version = "2.2.1", features = ["sqlite", "r2d2", "chrono"] } +diesel = { version = "2.2.1", features = ["sqlite", "postgres", "r2d2", "chrono"] } diesel_migrations = "2.2.0" dotenvy = "0.15.7" env_logger = "0.11.3" diff --git a/migrations_postgres/2024-02-14-014006_setup_schema/down.sql b/migrations_postgres/2024-02-14-014006_setup_schema/down.sql new file mode 100644 index 0000000..b425da1 --- /dev/null +++ b/migrations_postgres/2024-02-14-014006_setup_schema/down.sql @@ -0,0 +1,2 @@ +DROP TABLE parameters; +DROP TABLE pending_covenants; diff --git a/migrations_postgres/2024-02-14-014006_setup_schema/up.sql b/migrations_postgres/2024-02-14-014006_setup_schema/up.sql new file mode 100644 index 0000000..91863f9 --- /dev/null +++ b/migrations_postgres/2024-02-14-014006_setup_schema/up.sql @@ -0,0 +1,19 @@ +CREATE TABLE parameters ( + name VARCHAR PRIMARY KEY NOT NULL, + value VARCHAR NOT NULL +); + +CREATE TABLE pending_covenants ( + output_script BYTEA PRIMARY KEY NOT NULL, + status INT NOT NULL, + internal_key BYTEA NOT NULL, + preimage BYTEA NOT NULL, + swap_tree VARCHAR NOT NULL, + address BYTEA NOT NULL, + blinding_key BYTEA, + tx_id BYTEA, + tx_time TIMESTAMP, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX pending_covenants_status_idx ON pending_covenants (status); diff --git a/src/db/helpers.rs b/src/db/helpers.rs index f37202b..74bbb2d 100644 --- a/src/db/helpers.rs +++ b/src/db/helpers.rs @@ -9,18 +9,40 @@ use crate::db::schema::pending_covenants; const BLOCK_HEIGHT_NAME: &str = "block_height"; -pub fn upsert_block_height(con: db::Pool, height: u64) -> QueryResult { +pub fn upsert_block_height(con: db::Pool, height: u64) -> Result<(), diesel::result::Error> { let values = Parameter { name: BLOCK_HEIGHT_NAME.to_string(), value: height.to_string(), }; - insert_into(parameters::dsl::parameters) - .values(&values) - .on_conflict(parameters::dsl::name) - .do_update() - .set(parameters::dsl::value.eq(height.to_string())) - .execute(&mut con.get().unwrap()) + match parameters::dsl::parameters + .select(Parameter::as_select()) + .filter(parameters::dsl::name.eq(BLOCK_HEIGHT_NAME.to_string())) + .limit(1) + .load(&mut con.get().unwrap()) + { + Ok(res) => { + if res.is_empty() { + match insert_into(parameters::dsl::parameters) + .values(&values) + .execute(&mut con.get().unwrap()) + { + Ok(_) => Ok(()), + Err(err) => Err(err), + } + } else { + match update(parameters::dsl::parameters) + .filter(parameters::dsl::name.eq(BLOCK_HEIGHT_NAME.to_string())) + .set((parameters::dsl::value.eq(height.to_string()),)) + .execute(&mut con.get().unwrap()) + { + Ok(_) => Ok(()), + Err(err) => Err(err), + } + } + } + Err(err) => Err(err), + } } pub fn get_block_height(con: db::Pool) -> Option { diff --git a/src/db/mod.rs b/src/db/mod.rs index 77e07fd..1d7b5b3 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -1,31 +1,58 @@ +use std::error::Error; + +use diesel::prelude::*; use diesel::r2d2::ConnectionManager; -use diesel::sqlite::Sqlite; -use diesel::{Connection, SqliteConnection}; +use diesel::Connection; use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; -use std::error::Error; +use log::info; pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("./migrations"); +pub const MIGRATIONS_POSTGRES: EmbeddedMigrations = embed_migrations!("./migrations_postgres"); pub mod helpers; pub mod models; mod schema; -pub type DatabaseConnection = SqliteConnection; -pub type Pool = r2d2::Pool>; +#[derive(diesel::MultiConnection)] +pub enum AnyConnection { + Postgresql(PgConnection), + Sqlite(SqliteConnection), +} -pub fn establish_connection(url: &str) -> Result> { - run_migrations(&mut SqliteConnection::establish(url)?)?; +pub type Pool = r2d2::Pool>; - let manager = ConnectionManager::::new(url); +pub fn establish_connection(url: &str) -> Result> { + info!( + "Using {} database", + if is_postgres_connection_url(url) { + "PostgreSQL" + } else { + "SQLite" + } + ); + + let manager = ConnectionManager::new(url); let pool = Pool::builder().build(manager)?; + run_migrations(is_postgres_connection_url(url), &pool)?; + Ok(pool) } fn run_migrations( - connection: &mut impl MigrationHarness, + is_postgres: bool, + pool: &Pool, ) -> Result<(), Box> { - connection.run_pending_migrations(MIGRATIONS)?; + let mut con = pool.get()?; + con.run_pending_migrations(if is_postgres { + MIGRATIONS_POSTGRES + } else { + MIGRATIONS + })?; Ok(()) } + +fn is_postgres_connection_url(url: &str) -> bool { + url.starts_with("postgresql") +} diff --git a/src/main.rs b/src/main.rs index 94f60ad..2499cf2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,12 +24,20 @@ async fn main() { env_logger::init(); info!( - "Starting covclaim v{}-{}", + "Starting {} v{}-{}{}", + built_info::PKG_NAME, built_info::PKG_VERSION, - built_info::GIT_VERSION.unwrap_or("") + built_info::GIT_VERSION.unwrap_or(""), + if built_info::GIT_DIRTY.unwrap_or(false) { + "-dirty" + } else { + "" + } ); + debug!( - "Compiled with {} for {}", + "Compiled {} with {} for {}", + built_info::PROFILE, built_info::RUSTC_VERSION, built_info::TARGET ); @@ -101,8 +109,8 @@ async fn get_chain_backend() -> Arc> { .expect("ELEMENTS_PORT invalid"), env::var("ELEMENTS_COOKIE").expect("ELEMENTS_COOKIE must be set"), ) - .connect() - .await + .connect() + .await { Ok(client) => Box::new(client), Err(err) => {