diff --git a/Cargo.lock b/Cargo.lock index 0632e485..c7a922a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -49,6 +49,17 @@ version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e" +[[package]] +name = "ahash" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43bb833f0bf979d8475d38fbf09ed3b8a55e1885fe93ad3f93239fc6a4f17b98" +dependencies = [ + "getrandom 0.2.2", + "once_cell", + "version_check 0.9.3", +] + [[package]] name = "ansi_term" version = "0.11.0" @@ -128,6 +139,15 @@ dependencies = [ "pin-project-lite 0.2.6", ] +[[package]] +name = "atoi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616896e05fc0e2649463a93a15183c6a16bf03413a7af88ef1285ddedfa9cda5" +dependencies = [ + "num-traits", +] + [[package]] name = "atomic" version = "0.5.0" @@ -610,7 +630,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b1b9d958c2b1368a663f05538fc1b5975adce1e19f435acceae987aceeeb369" dependencies = [ "lazy_static", - "nom", + "nom 5.1.2", "serde", "toml", ] @@ -691,6 +711,21 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" +[[package]] +name = "crc" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10c2722795460108a7872e1cd933a85d6ec38abc4baecad51028f702da28889f" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403" + [[package]] name = "crc32fast" version = "1.2.1" @@ -723,6 +758,16 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f6cb3c7f5b8e51bc3ebb73a2327ad4abdbd119dc13223f14f961d2f38486756" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.5" @@ -937,6 +982,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + [[package]] name = "ecdsa_fun" version = "0.6.2-alpha.0" @@ -977,6 +1028,9 @@ name = "either" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +dependencies = [ + "serde", +] [[package]] name = "electrum-client" @@ -1175,6 +1229,17 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "futures-intrusive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62007592ac46aa7c2b6416f7deb9a8a8f63a01e0f1d6e1787d5630170db2b63e" +dependencies = [ + "futures-core", + "lock_api 0.4.5", + "parking_lot 0.11.2", +] + [[package]] name = "futures-io" version = "0.3.17" @@ -1362,7 +1427,25 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" dependencies = [ - "ahash", + "ahash 0.4.7", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash 0.7.4", +] + +[[package]] +name = "hashlink" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf" +dependencies = [ + "hashbrown 0.11.2", ] [[package]] @@ -1614,7 +1697,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3" dependencies = [ "autocfg 1.0.1", - "hashbrown", + "hashbrown 0.9.1", ] [[package]] @@ -2088,6 +2171,17 @@ dependencies = [ "libsecp256k1-core", ] +[[package]] +name = "libsqlite3-sys" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290b64917f8b0cb885d9de0f9959fe1f775d7fa12f1da2db9001c1c8ab60f89d" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "libz-sys" version = "1.1.2" @@ -2148,7 +2242,7 @@ version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f374d42cdfc1d7dbf3d3dec28afab2eb97ffbf43a3234d795b5986dbf4b90ba" dependencies = [ - "hashbrown", + "hashbrown 0.9.1", ] [[package]] @@ -2231,6 +2325,12 @@ dependencies = [ "syn", ] +[[package]] +name = "minimal-lexical" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c835948974f68e0bd58636fc6c5b1fbff7b297e3046f11b3b3c18bbac012c6d" + [[package]] name = "miniscript" version = "5.1.0" @@ -2429,6 +2529,17 @@ dependencies = [ "version_check 0.9.3", ] +[[package]] +name = "nom" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffd9d26838a953b4af82cbeb9f1592c6798916983959be223a7124e992742c1" +dependencies = [ + "memchr", + "minimal-lexical", + "version_check 0.9.3", +] + [[package]] name = "ntapi" version = "0.3.6" @@ -3536,6 +3647,7 @@ version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7f9e390c27c3c0ce8bc5d725f6e4d30a29d26659494aa4b17535f7522c5c950" dependencies = [ + "indexmap", "itoa", "ryu", "serde", @@ -3804,12 +3916,122 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "sqlformat" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4b7922be017ee70900be125523f38bdd644f4f06a1b16e8fa5a8ee8c34bffd4" +dependencies = [ + "itertools 0.10.1", + "nom 7.0.0", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4b94ab0f8c21ee4899b93b06451ef5d965f1a355982ee73684338228498440" +dependencies = [ + "sqlx-core", + "sqlx-macros", +] + +[[package]] +name = "sqlx-core" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec28b91a01e1fe286d6ba66f68289a2286df023fc97444e1fd86c2fd6d5dc026" +dependencies = [ + "ahash 0.7.4", + "atoi", + "bitflags", + "byteorder", + "bytes 1.0.1", + "crc", + "crossbeam-channel", + "crossbeam-queue", + "crossbeam-utils", + "either", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-util", + "hashlink", + "hex 0.4.3", + "itoa", + "libc", + "libsqlite3-sys", + "log 0.4.14", + "memchr", + "once_cell", + "parking_lot 0.11.2", + "percent-encoding 2.1.0", + "rustls 0.19.0", + "serde", + "sha2", + "smallvec", + "sqlformat", + "sqlx-rt", + "stringprep", + "thiserror", + "tokio-stream", + "url 2.2.2", + "webpki", + "webpki-roots 0.21.0", + "whoami", +] + +[[package]] +name = "sqlx-macros" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dc33c35d54774eed73d54568d47a6ac099aed8af5e1556a017c131be88217d5" +dependencies = [ + "dotenv", + "either", + "futures", + "heck", + "hex 0.4.3", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-rt", + "syn", + "url 2.2.2", +] + +[[package]] +name = "sqlx-rt" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14302b678d9c76b28f2e60115211e25e0aabc938269991745a169753dc00e35c" +dependencies = [ + "once_cell", + "tokio", + "tokio-rustls", +] + [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "stringprep" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "strsim" version = "0.8.0" @@ -3895,6 +4117,7 @@ dependencies = [ "bitcoin", "bitcoin-harness", "bmrng", + "chrono", "comfy-table", "config", "conquer-once", @@ -3930,6 +4153,7 @@ dependencies = [ "sigma_fun", "sled", "spectral", + "sqlx", "structopt", "strum", "tempfile", @@ -4508,6 +4732,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + [[package]] name = "universal-hash" version = "0.4.0" @@ -4790,6 +5020,16 @@ dependencies = [ "thiserror", ] +[[package]] +name = "whoami" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7741161a40200a867c96dfa5574544efa4178cf4c8f770b62dd1cc0362d7ae1" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + [[package]] name = "widestring" version = "0.4.3" diff --git a/swap/Cargo.toml b/swap/Cargo.toml index fa0179b1..3aced58c 100644 --- a/swap/Cargo.toml +++ b/swap/Cargo.toml @@ -19,6 +19,7 @@ bdk = "0.10" big-bytes = "1" bitcoin = { version = "0.26", features = [ "rand", "use-serde" ] } bmrng = "0.5" +chrono = "0.4" comfy-table = "4.1.1" config = { version = "0.11", default-features = false, features = [ "toml" ] } conquer-once = "0.3" @@ -48,6 +49,7 @@ serde_json = "1" serde_with = { version = "1", features = [ "macros" ] } sha2 = "0.9" sigma_fun = { git = "https://github.com/LLFourn/secp256kfun", default-features = false, features = [ "ed25519", "serde" ] } +sqlx = { version = "0.5", features = ["sqlite", "runtime-tokio-rustls", "offline"] } sled = "0.34" structopt = "0.3" strum = { version = "0.21", features = [ "derive" ] } @@ -60,6 +62,7 @@ tokio-util = { version = "0.6", features = [ "io" ] } toml = "0.5" torut = { version = "0.2", default-features = false, features = [ "v3", "control" ] } tracing = { version = "0.1", features = [ "attributes" ] } + tracing-appender = "0.1" tracing-futures = { version = "0.2", features = [ "std-future", "futures-03" ] } tracing-subscriber = { version = "0.2", default-features = false, features = [ "fmt", "ansi", "env-filter", "chrono", "tracing-log", "json" ] } diff --git a/swap/migrations/20210903050345_create_swaps_table.sql b/swap/migrations/20210903050345_create_swaps_table.sql new file mode 100644 index 00000000..741a45e6 --- /dev/null +++ b/swap/migrations/20210903050345_create_swaps_table.sql @@ -0,0 +1,25 @@ +CREATE TABLE if NOT EXISTS swap_states +( + id INTEGER PRIMARY KEY autoincrement NOT NULL, + swap_id TEXT NOT NULL, + entered_at TEXT NOT NULL, + state TEXT NOT NULL +); + +CREATE TABLE if NOT EXISTS monero_addresses +( + swap_id TEXT PRIMARY KEY NOT NULL, + address TEXT NOT NULL +); + +CREATE TABLE if NOT EXISTS peers +( + swap_id TEXT PRIMARY KEY NOT NULL, + peer_id TEXT NOT NULL +); + +CREATE TABLE if NOT EXISTS peer_addresses +( + peer_id TEXT NOT NULL, + address TEXT NOT NULL +); \ No newline at end of file diff --git a/swap/sqlite_dev_setup.sh b/swap/sqlite_dev_setup.sh new file mode 100644 index 00000000..d07a763a --- /dev/null +++ b/swap/sqlite_dev_setup.sh @@ -0,0 +1,6 @@ +# crated temporary DB +# run the migration scripts to create the tables +# prepare the sqlx-data.json rust mappings +DATABASE_URL=sqlite:tempdb cargo sqlx database create +DATABASE_URL=sqlite:tempdb cargo sqlx migrate run +DATABASE_URL=sqlite:./swap/tempdb cargo sqlx prepare -- --bin swap \ No newline at end of file diff --git a/swap/sqlx-data.json b/swap/sqlx-data.json new file mode 100644 index 00000000..61254fc2 --- /dev/null +++ b/swap/sqlx-data.json @@ -0,0 +1,139 @@ +{ + "db": "SQLite", + "081c729a0f1ad6e4ff3e13d6702c946bc4d37d50f40670b4f51d2efcce595aa6": { + "query": "\n SELECT peer_id\n FROM peers\n WHERE swap_id = ?\n ", + "describe": { + "columns": [ + { + "name": "peer_id", + "ordinal": 0, + "type_info": "Text" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false + ] + } + }, + "0ab84c094964968e96a3f2bf590d9ae92227d057386921e0e57165b887de3c75": { + "query": "\n insert into peer_addresses (\n peer_id,\n address\n ) values (?, ?);\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + } + }, + "1ec38c85e7679b2eb42b3df75d9098772ce44fdb8db3012d3c2410d828b74157": { + "query": "\n SELECT swap_id, state\n FROM (\n SELECT max(id), swap_id, state\n FROM swap_states\n GROUP BY swap_id\n )\n ", + "describe": { + "columns": [ + { + "name": "swap_id", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "state", + "ordinal": 1, + "type_info": "Text" + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + false, + false + ] + } + }, + "2a356078a41b321234adf2aa385b501749f907f7c422945a8bdda2b6274f5225": { + "query": "\n insert into peers (\n swap_id,\n peer_id\n ) values (?, ?);\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + } + }, + "50a5764546f69c118fa0b64120da50f51073d36257d49768de99ff863e3511e0": { + "query": "\n insert into monero_addresses (\n swap_id,\n address\n ) values (?, ?);\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + } + }, + "88f761a4f7a0429cad1df0b1bebb1c0a27b2a45656549b23076d7542cfa21ecf": { + "query": "\n SELECT state\n FROM swap_states\n WHERE swap_id = ?\n ORDER BY id desc\n LIMIT 1;\n\n ", + "describe": { + "columns": [ + { + "name": "state", + "ordinal": 0, + "type_info": "Text" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false + ] + } + }, + "a0eb85d04ee3842c52291dad4d225941d1141af735922fcbc665868997fce304": { + "query": "\n SELECT address\n FROM peer_addresses\n WHERE peer_id = ?\n ", + "describe": { + "columns": [ + { + "name": "address", + "ordinal": 0, + "type_info": "Text" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false + ] + } + }, + "b703032b4ddc627a1124817477e7a8e5014bdc694c36a14053ef3bb2fc0c69b0": { + "query": "\n insert into swap_states (\n swap_id,\n entered_at,\n state\n ) values (?, ?, ?);\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 3 + }, + "nullable": [] + } + }, + "ce270dd4a4b9615695a79864240c5401e2122077365e5e5a19408c068c7f9454": { + "query": "\n SELECT address\n FROM monero_addresses\n WHERE swap_id = ?\n ", + "describe": { + "columns": [ + { + "name": "address", + "ordinal": 0, + "type_info": "Text" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false + ] + } + } +} \ No newline at end of file diff --git a/swap/src/database.rs b/swap/src/database.rs index 056084f8..fba1b58c 100644 --- a/swap/src/database.rs +++ b/swap/src/database.rs @@ -1,16 +1,20 @@ -use crate::database::alice::Alice; -use crate::database::bob::Bob; -use crate::protocol::State; use std::fmt::Display; -use crate::protocol::bob::BobState; -use crate::protocol::alice::AliceState; + use serde::{Deserialize, Serialize}; +use crate::database::alice::Alice; +use crate::database::bob::Bob; +use crate::protocol::alice::AliceState; +use crate::protocol::bob::BobState; +use crate::protocol::State; + mod sled; +mod sqlite; mod alice; mod bob; pub use self::sled::SledDatabase; +pub use sqlite::SqliteDatabase; #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] pub enum Swap { diff --git a/swap/src/database/sqlite.rs b/swap/src/database/sqlite.rs new file mode 100644 index 00000000..7190119d --- /dev/null +++ b/swap/src/database/sqlite.rs @@ -0,0 +1,352 @@ +use uuid::Uuid; +use sqlx::sqlite::Sqlite; +use anyhow::Result; +use chrono::Utc; +use crate::protocol::{State, Database}; +use std::path::PathBuf; +use libp2p::{PeerId, Multiaddr}; +use crate::monero::Address; +use sqlx::{SqlitePool, Pool}; +use async_trait::async_trait; +use crate::database::Swap; +use std::str::FromStr; +use anyhow::Context; +use std::collections::HashMap; + +pub struct SqliteDatabase { + pool: Pool +} + +impl SqliteDatabase { + pub async fn open(path: PathBuf) -> Result where Self: std::marker::Sized { + let path_str = format!("sqlite:{}", path.as_path().display()); + let pool = SqlitePool::connect(&path_str) + .await?; + Ok(Self { pool }) + } + + pub async fn run_migrations(&mut self) -> anyhow::Result<()> { + sqlx::migrate!("./migrations").run(&self.pool).await?; + Ok(()) + } +} + +#[async_trait] +impl Database for SqliteDatabase { + async fn insert_peer_id(&self, swap_id: Uuid, peer_id: PeerId) -> Result<()> { + let mut conn = self.pool.acquire().await?; + + let swap_id = swap_id.to_string(); + let peer_id = peer_id.to_string(); + + sqlx::query!( + r#" + insert into peers ( + swap_id, + peer_id + ) values (?, ?); + "#, + swap_id, peer_id + ).execute(&mut conn).await?; + + Ok(()) + } + + async fn get_peer_id(&self, swap_id: Uuid) -> Result { + let mut conn = self.pool.acquire().await?; + + let swap_id = swap_id.to_string(); + + let row = sqlx::query!(r#" + SELECT peer_id + FROM peers + WHERE swap_id = ? + "#, + swap_id + ).fetch_one(&mut conn). await?; + + let peer_id = PeerId::from_str(&row.peer_id)?; + Ok(peer_id) + } + + async fn insert_monero_address(&self, swap_id: Uuid, address: Address) -> Result<()> { + let mut conn = self.pool.acquire().await?; + + let swap_id = swap_id.to_string(); + let address = address.to_string(); + + sqlx::query!( + r#" + insert into monero_addresses ( + swap_id, + address + ) values (?, ?); + "#, + swap_id, address + ).execute(&mut conn).await?; + + Ok(()) + } + + async fn get_monero_address(&self, swap_id: Uuid) -> Result
{ + let mut conn = self.pool.acquire().await?; + + let swap_id = swap_id.to_string(); + + let row = sqlx::query!(r#" + SELECT address + FROM monero_addresses + WHERE swap_id = ? + "#, + swap_id + ).fetch_one(&mut conn). await?; + + let address = row.address.parse()?; + + Ok(address) + } + + async fn insert_address(&self, peer_id: PeerId, address: Multiaddr) -> Result<()> { + let mut conn = self.pool.acquire().await?; + + let peer_id = peer_id.to_string(); + let address = address.to_string(); + + sqlx::query!( + r#" + insert into peer_addresses ( + peer_id, + address + ) values (?, ?); + "#, + peer_id, address + ).execute(&mut conn).await?; + + Ok(()) + } + + async fn get_addresses(&self, peer_id: PeerId) -> Result> { + let mut conn = self.pool.acquire().await?; + + let peer_id = peer_id.to_string(); + + let rows = sqlx::query!(r#" + SELECT address + FROM peer_addresses + WHERE peer_id = ? + "#, + peer_id, + ).fetch_all(&mut conn). await?; + + let addresses = rows.iter() + .map(|row| { + let multiaddr = Multiaddr::from_str(&row.address)?; + Ok(multiaddr) + }) + .collect::>>(); + + addresses + } + + async fn insert_latest_state(&self, swap_id: Uuid, state: State) -> Result<()> { + let mut conn = self.pool.acquire().await?; + let entered_at = Utc::now(); + + let swap_id = swap_id.to_string(); + let swap = serde_json::to_string(&Swap::from(state))?; + let entered_at = entered_at.to_string(); + + sqlx::query!( + r#" + insert into swap_states ( + swap_id, + entered_at, + state + ) values (?, ?, ?); + "#, + swap_id, + entered_at, + swap + ).execute(&mut conn).await?; + + Ok(()) + } + + async fn get_state(&self, swap_id: Uuid) -> Result { + let mut conn = self.pool.acquire().await?; + let swap_id = swap_id.to_string(); + let row = sqlx::query!( + r#" + SELECT state + FROM swap_states + WHERE swap_id = ? + ORDER BY id desc + LIMIT 1; + + "#, + swap_id + ).fetch_all(&mut conn).await?; + + let row = row.first().context(format!("No state in database for swap: {}", swap_id))?; + let swap: Swap = serde_json::from_str(&row.state)?; + + Ok(swap.into()) + } + + async fn all(&self) -> Result> { + let mut conn = self.pool.acquire().await?; + let rows = sqlx::query!( + r#" + SELECT swap_id, state + FROM ( + SELECT max(id), swap_id, state + FROM swap_states + GROUP BY swap_id + ) + "# + ).fetch_all(&mut conn).await?; + + let result = rows.iter().map(|row|{ + let swap_id = Uuid::from_str(&row.swap_id)?; + let state = match serde_json::from_str::(&row.state) { + Ok(a) => Ok(State::from(a)), + Err(e) => Err(e) + }?; + Ok((swap_id, state)) + }).collect::>>(); + + result + } + + async fn unfinished(&self, unfinished: fn(State) -> bool) -> Result> { + Ok(self.all() + .await? + .into_iter() + .filter(|(_swap_id, state)| unfinished(state.clone())) + .collect()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs::File; + use tempfile::tempdir; + use crate::protocol::alice::AliceState; + use crate::protocol::bob::BobState; + + #[tokio::test] + async fn test_insert_and_load_state() { + let db = setup_test_db().await.unwrap(); + + let state_1 = State::Alice(AliceState::BtcRedeemed); + let swap_id_1 = Uuid::new_v4(); + + db.insert_latest_state( swap_id_1, state_1).await.unwrap(); + + let state_1 = State::Alice(AliceState::BtcRedeemed); + + db.insert_latest_state( swap_id_1, state_1.clone()).await.unwrap(); + + let state_1_loaded = db.get_state( swap_id_1).await.unwrap(); + + assert_eq!(state_1, state_1_loaded); + } + + #[tokio::test] + async fn test_retrieve_all_latest_states() { + let db = setup_test_db().await.unwrap(); + + let state_1 = State::Alice(AliceState::BtcRedeemed); + let state_2 = State::Alice(AliceState::BtcPunished); + let state_3 = State::Alice(AliceState::SafelyAborted); + let state_4 = State::Bob(BobState::SafelyAborted); + let swap_id_1 = Uuid::new_v4(); + let swap_id_2 = Uuid::new_v4(); + + db.insert_latest_state(swap_id_1, state_1.clone()).await.unwrap(); + db.insert_latest_state(swap_id_1, state_2.clone()).await.unwrap(); + db.insert_latest_state(swap_id_1, state_3.clone()).await.unwrap(); + db.insert_latest_state(swap_id_2, state_4.clone()).await.unwrap(); + + let latest_loaded = db.all().await.unwrap(); + + assert_eq!(latest_loaded.len(), 2); + + assert!(latest_loaded.contains(&(swap_id_1, state_3))); + assert!(latest_loaded.contains(&(swap_id_2, state_4))); + + assert!(!latest_loaded.contains(&(swap_id_1, state_1))); + assert!(!latest_loaded.contains(&(swap_id_1, state_2))); + } + + #[tokio::test] + async fn test_insert_load_monero_address() -> Result<()> { + + let db = setup_test_db().await?; + + let swap_id = Uuid::new_v4(); + let monero_address = "53gEuGZUhP9JMEBZoGaFNzhwEgiG7hwQdMCqFxiyiTeFPmkbt1mAoNybEUvYBKHcnrSgxnVWgZsTvRBaHBNXPa8tHiCU51a".parse()?; + + db.insert_monero_address(swap_id, monero_address).await?; + + let loaded_monero_address = db.get_monero_address(swap_id).await?; + + assert_eq!(monero_address, loaded_monero_address); + + Ok(()) + } + + #[tokio::test] + async fn test_insert_and_load_multiaddr() -> Result<()> { + let db = setup_test_db().await?; + + let peer_id = PeerId::random(); + let multiaddr1 = "/ip4/127.0.0.1".parse::()?; + let multiaddr2 = "/ip4/127.0.0.2".parse::()?; + + db.insert_address(peer_id, multiaddr1.clone()).await?; + db.insert_address(peer_id, multiaddr2.clone()).await?; + + let loaded_multiaddr = db.get_addresses(peer_id).await?; + + assert!(loaded_multiaddr.contains(&multiaddr1)); + assert!(loaded_multiaddr.contains(&multiaddr2)); + assert_eq!(loaded_multiaddr.len(), 2); + + Ok(()) + } + + #[tokio::test] + async fn test_insert_and_load_peer_id() -> Result<()> { + let db = setup_test_db().await?; + + let peer_id = PeerId::random(); + let multiaddr1 = "/ip4/127.0.0.1".parse::()?; + let multiaddr2 = "/ip4/127.0.0.2".parse::()?; + + db.insert_address(peer_id, multiaddr1.clone()).await?; + db.insert_address(peer_id, multiaddr2.clone()).await?; + + let loaded_multiaddr = db.get_addresses(peer_id).await?; + + assert!(loaded_multiaddr.contains(&multiaddr1)); + assert!(loaded_multiaddr.contains(&multiaddr2)); + assert_eq!(loaded_multiaddr.len(), 2); + + Ok(()) + } + + async fn setup_test_db() -> Result { + let temp_db = tempdir().unwrap().into_path().join("tempdb"); + + // file has to exist in order to connect with sqlite + File::create(temp_db.clone()).unwrap(); + + let mut db = SqliteDatabase::open(temp_db).await?; + + db.run_migrations().await.unwrap(); + + Ok(db) + } +}