mirror of
https://github.com/aljazceru/cdk.git
synced 2025-12-20 14:14:49 +01:00
feat: Add support for sqlcipher
This commit is contained in:
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@@ -94,6 +94,7 @@ jobs:
|
||||
-p cdk --no-default-features --features "mint swagger",
|
||||
-p cdk-redb,
|
||||
-p cdk-sqlite,
|
||||
-p cdk-sqlite --features sqlcipher,
|
||||
-p cdk-axum --no-default-features,
|
||||
-p cdk-axum --no-default-features --features swagger,
|
||||
-p cdk-axum --no-default-features --features redis,
|
||||
@@ -104,10 +105,12 @@ jobs:
|
||||
-p cdk-lnbits,
|
||||
-p cdk-fake-wallet,
|
||||
--bin cdk-cli,
|
||||
--bin cdk-cli --features sqlcipher,
|
||||
--bin cdk-mintd,
|
||||
--bin cdk-mintd --features redis,
|
||||
--bin cdk-mintd --features redb,
|
||||
--bin cdk-mintd --features "redis swagger redb",
|
||||
--bin cdk-mintd --features sqlcipher,
|
||||
--bin cdk-mintd --no-default-features --features lnd,
|
||||
--bin cdk-mintd --no-default-features --features cln,
|
||||
--bin cdk-mintd --no-default-features --features lnbits,
|
||||
|
||||
@@ -9,6 +9,9 @@ repository.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[features]
|
||||
sqlcipher = ["cdk-sqlite/sqlcipher"]
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
bip39.workspace = true
|
||||
|
||||
@@ -31,6 +31,10 @@ struct Cli {
|
||||
/// Database engine to use (sqlite/redb)
|
||||
#[arg(short, long, default_value = "sqlite")]
|
||||
engine: String,
|
||||
/// Database password for sqlcipher
|
||||
#[cfg(feature = "sqlcipher")]
|
||||
#[arg(long)]
|
||||
password: Option<String>,
|
||||
/// Path to working dir
|
||||
#[arg(short, long)]
|
||||
work_dir: Option<PathBuf>,
|
||||
@@ -106,7 +110,15 @@ async fn main() -> Result<()> {
|
||||
match args.engine.as_str() {
|
||||
"sqlite" => {
|
||||
let sql_path = work_dir.join("cdk-cli.sqlite");
|
||||
#[cfg(not(feature = "sqlcipher"))]
|
||||
let sql = WalletSqliteDatabase::new(&sql_path).await?;
|
||||
#[cfg(feature = "sqlcipher")]
|
||||
let sql = {
|
||||
match args.password {
|
||||
Some(pass) => WalletSqliteDatabase::new(&sql_path, pass).await?,
|
||||
None => bail!("Missing database password"),
|
||||
}
|
||||
};
|
||||
|
||||
sql.migrate().await;
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ redis = ["cdk-axum/redis"]
|
||||
management-rpc = ["cdk-mint-rpc"]
|
||||
# MSRV is not commited to with redb enabled
|
||||
redb = ["dep:cdk-redb"]
|
||||
sqlcipher = ["cdk-sqlite/sqlcipher"]
|
||||
cln = ["dep:cdk-cln"]
|
||||
lnd = ["dep:cdk-lnd"]
|
||||
lnbits = ["dep:cdk-lnbits"]
|
||||
|
||||
@@ -12,6 +12,9 @@ pub struct CLIArgs {
|
||||
required = false
|
||||
)]
|
||||
pub work_dir: Option<PathBuf>,
|
||||
#[cfg(feature = "sqlcipher")]
|
||||
#[arg(short, long, help = "Database password for sqlcipher", required = true)]
|
||||
pub password: String,
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
|
||||
@@ -111,7 +111,10 @@ async fn main() -> anyhow::Result<()> {
|
||||
match settings.database.engine {
|
||||
DatabaseEngine::Sqlite => {
|
||||
let sql_db_path = work_dir.join("cdk-mintd.sqlite");
|
||||
#[cfg(not(feature = "sqlcipher"))]
|
||||
let sqlite_db = MintSqliteDatabase::new(&sql_db_path).await?;
|
||||
#[cfg(feature = "sqlcipher")]
|
||||
let sqlite_db = MintSqliteDatabase::new(&sql_db_path, args.password).await?;
|
||||
|
||||
sqlite_db.migrate().await;
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ rust-version = "1.75.0" # MSRV
|
||||
default = ["mint", "wallet"]
|
||||
mint = ["cdk-common/mint"]
|
||||
wallet = ["cdk-common/wallet"]
|
||||
sqlcipher = ["libsqlite3-sys"]
|
||||
|
||||
[dependencies]
|
||||
async-trait.workspace = true
|
||||
@@ -26,6 +27,7 @@ sqlx = { version = "0.6.3", default-features = false, features = [
|
||||
"migrate",
|
||||
"uuid",
|
||||
] }
|
||||
libsqlite3-sys = { version = "0.24.1", features = ["bundled-sqlcipher"], optional = true }
|
||||
thiserror.workspace = true
|
||||
tokio.workspace = true
|
||||
tracing.workspace = true
|
||||
|
||||
@@ -13,6 +13,7 @@ The following crate feature flags are available:
|
||||
|-------------|:-------:|------------------------------------|
|
||||
| `wallet` | Yes | Enable cashu wallet features |
|
||||
| `mint` | Yes | Enable cashu mint wallet features |
|
||||
| `sqlcipher` | No | Enable encrypted database |
|
||||
|
||||
## Implemented [NUTs](https://github.com/cashubtc/nuts/):
|
||||
|
||||
|
||||
@@ -5,7 +5,10 @@ use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions};
|
||||
use sqlx::{Error, Pool, Sqlite};
|
||||
|
||||
#[inline(always)]
|
||||
pub async fn create_sqlite_pool(path: &str) -> Result<Pool<Sqlite>, Error> {
|
||||
pub async fn create_sqlite_pool(
|
||||
path: &str,
|
||||
#[cfg(feature = "sqlcipher")] password: String,
|
||||
) -> Result<Pool<Sqlite>, Error> {
|
||||
let db_options = SqliteConnectOptions::from_str(path)?
|
||||
.busy_timeout(Duration::from_secs(10))
|
||||
.read_only(false)
|
||||
@@ -17,6 +20,9 @@ pub async fn create_sqlite_pool(path: &str) -> Result<Pool<Sqlite>, Error> {
|
||||
.shared_cache(true)
|
||||
.create_if_missing(true);
|
||||
|
||||
#[cfg(feature = "sqlcipher")]
|
||||
let db_options = db_options.pragma("key", password);
|
||||
|
||||
let pool = SqlitePoolOptions::new()
|
||||
.min_connections(1)
|
||||
.max_connections(1)
|
||||
|
||||
@@ -12,7 +12,10 @@ use super::MintSqliteDatabase;
|
||||
|
||||
/// Creates a new in-memory [`MintSqliteDatabase`] instance
|
||||
pub async fn empty() -> Result<MintSqliteDatabase, database::Error> {
|
||||
#[cfg(not(feature = "sqlcipher"))]
|
||||
let db = MintSqliteDatabase::new(":memory:").await?;
|
||||
#[cfg(feature = "sqlcipher")]
|
||||
let db = MintSqliteDatabase::new(":memory:", "memory".to_string()).await?;
|
||||
db.migrate().await;
|
||||
Ok(db)
|
||||
}
|
||||
|
||||
@@ -68,12 +68,25 @@ impl MintSqliteDatabase {
|
||||
}
|
||||
|
||||
/// Create new [`MintSqliteDatabase`]
|
||||
#[cfg(not(feature = "sqlcipher"))]
|
||||
pub async fn new<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
pool: create_sqlite_pool(path.as_ref().to_str().ok_or(Error::InvalidDbPath)?).await?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create new [`MintSqliteDatabase`]
|
||||
#[cfg(feature = "sqlcipher")]
|
||||
pub async fn new<P: AsRef<Path>>(path: P, password: String) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
pool: create_sqlite_pool(
|
||||
path.as_ref().to_str().ok_or(Error::InvalidDbPath)?,
|
||||
password,
|
||||
)
|
||||
.await?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Migrate [`MintSqliteDatabase`]
|
||||
pub async fn migrate(&self) {
|
||||
sqlx::migrate!("./src/mint/migrations")
|
||||
|
||||
@@ -33,12 +33,25 @@ pub struct WalletSqliteDatabase {
|
||||
|
||||
impl WalletSqliteDatabase {
|
||||
/// Create new [`WalletSqliteDatabase`]
|
||||
#[cfg(not(feature = "sqlcipher"))]
|
||||
pub async fn new<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
pool: create_sqlite_pool(path.as_ref().to_str().ok_or(Error::InvalidDbPath)?).await?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create new [`WalletSqliteDatabase`]
|
||||
#[cfg(feature = "sqlcipher")]
|
||||
pub async fn new<P: AsRef<Path>>(path: P, password: String) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
pool: create_sqlite_pool(
|
||||
path.as_ref().to_str().ok_or(Error::InvalidDbPath)?,
|
||||
password,
|
||||
)
|
||||
.await?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Migrate [`WalletSqliteDatabase`]
|
||||
pub async fn migrate(&self) {
|
||||
sqlx::migrate!("./src/wallet/migrations")
|
||||
@@ -954,3 +967,32 @@ fn sqlite_row_to_proof_info(row: &SqliteRow) -> Result<ProofInfo, Error> {
|
||||
unit: CurrencyUnit::from_str(&row_unit).map_err(Error::from)?,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::env::temp_dir;
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg(feature = "sqlcipher")]
|
||||
async fn test_sqlcipher() {
|
||||
use super::*;
|
||||
let path = std::env::temp_dir()
|
||||
.to_path_buf()
|
||||
.join(format!("cdk-test-{}.sqlite", uuid::Uuid::new_v4()));
|
||||
let db = WalletSqliteDatabase::new(path, "password".to_string())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.migrate().await;
|
||||
|
||||
// do something simple to test the database
|
||||
let pk = PublicKey::from_hex(
|
||||
"02194603ffa36356f4a56b7df9371fc3192472351453ec7398b8da8117e7c3e104",
|
||||
)
|
||||
.unwrap();
|
||||
let last_checked = 6969;
|
||||
db.add_nostr_last_checked(pk, last_checked).await.unwrap();
|
||||
let res = db.get_nostr_last_checked(&pk).await.unwrap();
|
||||
assert_eq!(res, Some(last_checked));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user