Skip to content

Commit 67a0912

Browse files
committed
Add sqlite implementation with tests
chore: * rename crate * update .gitignore
1 parent 72be78e commit 67a0912

File tree

14 files changed

+951
-67
lines changed

14 files changed

+951
-67
lines changed

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,8 @@ logs/*
4343
/.simplicity-dex.config.toml
4444
/.cache
4545
taker/
46-
simplicity-dex
46+
simplicity-dex
47+
48+
# DB
49+
**/dcd_cache.db
50+
/db

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ clap = { version = "4.5.49", features = ["derive"] }
2121
config = { version = "0.15.18" }
2222
contracts = { git = "https://github.com/BlockstreamResearch/simplicity-contracts.git", rev = "baa8ab7", package = "contracts" }
2323
contracts-adapter = { git = "https://github.com/BlockstreamResearch/simplicity-contracts.git", rev = "baa8ab7", package = "contracts-adapter" }
24+
coin-selection = { path = "./crate/coin-selection" }
2425
dex-nostr-relay = { path = "./crates/dex-nostr-relay" }
2526
dirs = { version = "6.0.0" }
2627
dotenvy = { version = "0.15.7" }
Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[package]
2-
name = "coin_selection"
2+
name = "coin-selection"
33
version.workspace = true
44
edition.workspace = true
55
rust-version.workspace = true
@@ -12,8 +12,13 @@ async-trait = { workspace = true }
1212
bincode = { workspace = true }
1313
contracts = { workspace = true }
1414
contracts-adapter = { workspace = true }
15+
global-utils = { workspace = true }
16+
serde = { workspace = true }
1517
simplicity-lang = { workspace = true }
1618
simplicityhl = { workspace = true }
1719
simplicityhl-core = { workspace = true }
1820
sqlx = { workspace = true }
19-
tokio = { workspace = true }
21+
tokio = { workspace = true }
22+
23+
[dev-dependencies]
24+
dotenvy = { workspace = true }
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
CREATE TABLE IF NOT EXISTS outpoints
2+
(
3+
id INTEGER PRIMARY KEY AUTOINCREMENT,
4+
tx_id VARYING CHARACTER(64) NOT NULL,
5+
vout INTEGER NOT NULL,
6+
owner_address TEXT NOT NULL,
7+
asset_id TEXT NOT NULL,
8+
spent BOOLEAN NOT NULL DEFAULT FALSE,
9+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
10+
UNIQUE (tx_id, vout)
11+
);
12+
13+
CREATE TABLE IF NOT EXISTS dcd_params
14+
(
15+
id INTEGER PRIMARY KEY AUTOINCREMENT,
16+
taproot_pubkey_gen TEXT NOT NULL UNIQUE,
17+
dcd_args_blob BLOB NOT NULL,
18+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
19+
);
20+
21+
CREATE TABLE IF NOT EXISTS dcd_token_entropies
22+
(
23+
id INTEGER PRIMARY KEY AUTOINCREMENT,
24+
taproot_pubkey_gen TEXT NOT NULL UNIQUE,
25+
token_entropies_blob BLOB NOT NULL,
26+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
27+
);
28+
29+
-- todo: create indexes
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
pub mod common;
12
pub mod sqlite_db;
23
pub mod types;
34

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
use crate::types::{CoinSelectionStorage, DcdContractTokenEntropies, GetTokenFilter, OutPointInfo, OutPointInfoRaw};
2+
use crate::types::{DcdParamsStorage, EntropyStorage, Result};
3+
use anyhow::{Context, anyhow};
4+
use async_trait::async_trait;
5+
use contracts::DCDArguments;
6+
use simplicity::bitcoin::OutPoint;
7+
use sqlx::{Connection, Sqlite, SqlitePool, migrate::MigrateDatabase};
8+
use std::path::PathBuf;
9+
10+
const CARGO_MANIFEST_DIR: &str = env!("CARGO_MANIFEST_DIR");
11+
12+
#[derive(Clone)]
13+
pub struct SqliteRepo {
14+
pool: SqlitePool,
15+
}
16+
17+
const SQLITE_DB_NAME: &str = "dcd_cache.db";
18+
19+
impl SqliteRepo {
20+
pub async fn from_url(db_url: &str) -> Result<Self> {
21+
Self::create_database(db_url).await?;
22+
let pool = SqlitePool::connect(db_url).await?;
23+
sqlx::migrate!().run(&pool).await?;
24+
Ok(Self { pool })
25+
}
26+
27+
#[inline]
28+
async fn create_database(database_url: &str) -> Result<()> {
29+
if !Sqlite::database_exists(database_url).await? {
30+
Sqlite::create_database(database_url).await?;
31+
}
32+
Ok(())
33+
}
34+
35+
pub async fn from_pool(pool: SqlitePool) -> Result<Self> {
36+
sqlx::migrate!().run(&pool).await?;
37+
Ok(Self { pool })
38+
}
39+
40+
pub async fn new() -> Result<Self> {
41+
let db_path = Self::default_db_path()?;
42+
let sqlite_db_url = Self::get_db_path(
43+
db_path
44+
.to_str()
45+
.with_context(|| anyhow!("No path found in db_path: '{db_path:?}'"))?,
46+
);
47+
Self::from_url(&sqlite_db_url).await
48+
}
49+
50+
#[inline]
51+
fn get_db_path(path: impl AsRef<str>) -> String {
52+
format!("sqlite://{}", path.as_ref())
53+
}
54+
55+
fn default_db_path() -> Result<PathBuf> {
56+
let manifest_dir = PathBuf::from(CARGO_MANIFEST_DIR);
57+
let workspace_root = manifest_dir
58+
.parent()
59+
.and_then(|p| p.parent())
60+
.ok_or_else(|| anyhow::anyhow!("Could not determine workspace root"))?;
61+
62+
let db_dir = workspace_root.join("db");
63+
64+
if !db_dir.exists() {
65+
std::fs::create_dir_all(&db_dir)?;
66+
}
67+
68+
Ok(db_dir.join(SQLITE_DB_NAME))
69+
}
70+
71+
pub async fn healthcheck(&self) -> Result<()> {
72+
self.pool.acquire().await?.ping().await?;
73+
Ok(())
74+
}
75+
}
76+
77+
#[async_trait]
78+
impl CoinSelectionStorage for SqliteRepo {
79+
async fn mark_outpoints_spent(&self, outpoints: &[OutPoint]) -> Result<()> {
80+
// mark given outpoints as spent in a single transaction
81+
let mut tx = self.pool.begin().await?;
82+
for op in outpoints {
83+
sqlx::query(
84+
r#"
85+
UPDATE outpoints
86+
SET spent = 1
87+
WHERE tx_id = ? AND vout = ?
88+
"#,
89+
)
90+
.bind(op.txid.to_string())
91+
.bind(op.vout as i64)
92+
.execute(&mut *tx)
93+
.await?;
94+
}
95+
tx.commit().await?;
96+
Ok(())
97+
}
98+
99+
async fn add_outpoint(&self, info: OutPointInfo) -> Result<()> {
100+
sqlx::query(
101+
r#"
102+
INSERT INTO outpoints (tx_id, vout, owner_address, asset_id, spent)
103+
VALUES (?, ?, ?, ?, ?)
104+
ON CONFLICT(tx_id, vout) DO UPDATE
105+
SET owner_address = excluded.owner_address,
106+
asset_id = excluded.asset_id,
107+
spent = excluded.spent
108+
"#,
109+
)
110+
.bind(info.outpoint.txid.to_string())
111+
.bind(info.outpoint.vout as i64)
112+
.bind(info.owner_addr)
113+
.bind(info.asset_id)
114+
.bind(info.spent)
115+
.execute(&self.pool)
116+
.await?;
117+
118+
Ok(())
119+
}
120+
121+
async fn get_token_outpoints(&self, filter: GetTokenFilter) -> Result<Vec<OutPointInfoRaw>> {
122+
let base = "SELECT id, tx_id, vout, owner_address, asset_id, spent FROM outpoints";
123+
let where_clause = filter.get_sql_filter();
124+
let query = format!("{base}{where_clause}");
125+
126+
let mut sql_query = sqlx::query_as::<_, (i64, String, i64, String, String, bool)>(&query);
127+
128+
if let Some(asset_id) = &filter.asset_id {
129+
sql_query = sql_query.bind(asset_id);
130+
}
131+
if let Some(spent) = filter.spent {
132+
sql_query = sql_query.bind(spent);
133+
}
134+
if let Some(owner) = &filter.owner {
135+
sql_query = sql_query.bind(owner);
136+
}
137+
138+
let rows = sql_query.fetch_all(&self.pool).await?;
139+
140+
let outpoints = rows
141+
.into_iter()
142+
.filter_map(|(id, tx_id, vout, owner_address, asset_id, spent)| {
143+
let tx_id = tx_id.parse().ok()?;
144+
let vout = vout as u32;
145+
Some(OutPointInfoRaw {
146+
id: id as u64,
147+
outpoint: OutPoint::new(tx_id, vout),
148+
owner_address,
149+
asset_id,
150+
spent,
151+
})
152+
})
153+
.collect();
154+
155+
Ok(outpoints)
156+
}
157+
}
158+
159+
#[async_trait]
160+
impl DcdParamsStorage for SqliteRepo {
161+
async fn add_dcd_params(&self, taproot_pubkey_gen: &str, dcd_args: &DCDArguments) -> Result<()> {
162+
let serialized = bincode::encode_to_vec(dcd_args, bincode::config::standard())?;
163+
164+
sqlx::query(
165+
"INSERT INTO dcd_params (taproot_pubkey_gen, dcd_args_blob)
166+
VALUES (?, ?)
167+
ON CONFLICT(taproot_pubkey_gen) DO UPDATE SET dcd_args_blob = excluded.dcd_args_blob",
168+
)
169+
.bind(taproot_pubkey_gen)
170+
.bind(serialized)
171+
.execute(&self.pool)
172+
.await?;
173+
174+
Ok(())
175+
}
176+
177+
async fn get_dcd_params(&self, taproot_pubkey_gen: &str) -> Result<Option<DCDArguments>> {
178+
let row = sqlx::query_as::<_, (Vec<u8>,)>("SELECT dcd_args_blob FROM dcd_params WHERE taproot_pubkey_gen = ?")
179+
.bind(taproot_pubkey_gen)
180+
.fetch_optional(&self.pool)
181+
.await?;
182+
183+
match row {
184+
Some((blob,)) => {
185+
let (dcd_args, _) = bincode::decode_from_slice(&blob, bincode::config::standard())?;
186+
Ok(Some(dcd_args))
187+
}
188+
None => Ok(None),
189+
}
190+
}
191+
}
192+
193+
#[async_trait]
194+
impl EntropyStorage for SqliteRepo {
195+
async fn add_dcd_contract_token_entropies(
196+
&self,
197+
taproot_pubkey_gen: &str,
198+
token_entropies: DcdContractTokenEntropies,
199+
) -> Result<()> {
200+
let serialized = bincode::encode_to_vec(&token_entropies, bincode::config::standard())?;
201+
202+
sqlx::query(
203+
"INSERT INTO dcd_token_entropies (taproot_pubkey_gen, token_entropies_blob)
204+
VALUES (?, ?)
205+
ON CONFLICT(taproot_pubkey_gen) DO UPDATE SET token_entropies_blob = excluded.token_entropies_blob",
206+
)
207+
.bind(taproot_pubkey_gen)
208+
.bind(serialized)
209+
.execute(&self.pool)
210+
.await?;
211+
212+
Ok(())
213+
}
214+
215+
async fn get_dcd_contract_token_entropies(
216+
&self,
217+
taproot_pubkey_gen: &str,
218+
) -> Result<Option<DcdContractTokenEntropies>> {
219+
let row = sqlx::query_as::<_, (Vec<u8>,)>(
220+
"SELECT token_entropies_blob FROM dcd_token_entropies WHERE taproot_pubkey_gen = ?",
221+
)
222+
.bind(taproot_pubkey_gen)
223+
.fetch_optional(&self.pool)
224+
.await?;
225+
226+
match row {
227+
Some((blob,)) => {
228+
let (token_entropies, _) = bincode::decode_from_slice(&blob, bincode::config::standard())?;
229+
Ok(Some(token_entropies))
230+
}
231+
None => Ok(None),
232+
}
233+
}
234+
}

0 commit comments

Comments
 (0)