Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ home = "0.5.9"
rusqlite = { version = "0.31.0", features = ["sqlcipher"] }
diesel = { version = "2.1.6", features = ["sqlite", "chrono", "r2d2"] }
diesel_migrations = { version = "2.1.0", features = ["sqlite"] }
uuid = { version = "1.8", features = ["v4"] }

bitcoin = { version = "0.29.2", features = ["base64"] }
bip39 = "2.0.0"
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,9 @@ just run
```
just release
```

4. Reset local DB (for init, schema generation, etc.)

```
just reset-db
```
9 changes: 9 additions & 0 deletions diesel.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# For documentation on how to configure this file,
# see https://diesel.rs/guides/configuring-diesel-cli

[print_schema]
file = "src/db_models/schema.rs"
custom_type_derives = ["diesel::query_builder::QueryId"]

[migrations_directory]
dir = "migrations"
1 change: 1 addition & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
pkgs.xorg.libXcursor
pkgs.xorg.libXi
pkgs.xorg.libXrandr
pkgs.diesel-cli
] ++ pkgs.lib.optionals pkgs.stdenv.isDarwin [
pkgs.darwin.apple_sdk.frameworks.AppKit
pkgs.darwin.apple_sdk.frameworks.CoreText
Expand Down
Binary file added harbor.sqlite
Binary file not shown.
3 changes: 3 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ release:

clippy:
cargo clippy --all-features --tests -- -D warnings

reset-db:
diesel migration revert --all --database-url=harbor.sqlite && diesel migration run --database-url=harbor.sqlite
Empty file added migrations/.keep
Empty file.
1 change: 1 addition & 0 deletions migrations/2024-05-13-234832_create_config/down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DROP TABLE profile;
4 changes: 4 additions & 0 deletions migrations/2024-05-13-234832_create_config/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
CREATE TABLE profile (
id CHAR(36) PRIMARY KEY NOT NULL,
seed_words CHAR(255) NOT NULL
);
25 changes: 22 additions & 3 deletions src/conf.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
use bip39::{Language, Mnemonic};
use bitcoin::Network;
use log::info;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc;

use crate::{db::DBConnection, db_models::NewProfile};

/// The directory where all application data is stored
/// Defaults to ~/.harbor, if we're on a test network
Expand All @@ -17,8 +22,22 @@ pub fn data_dir(network: Network) -> PathBuf {
}

// todo store in encrypted database
pub fn get_mnemonic(_network: Network) -> anyhow::Result<Mnemonic> {
let mnemonic = Mnemonic::generate_in(Language::English, 12)?;
pub fn get_mnemonic(db: Arc<dyn DBConnection + Send + Sync>) -> anyhow::Result<Mnemonic> {
match db.get_seed()? {
Some(m) => {
info!("retrieved existing seed");
Ok(Mnemonic::from_str(&m)?)
}
None => {
let new_profile = NewProfile {
id: uuid::Uuid::new_v4().to_string(),
seed_words: Mnemonic::generate_in(Language::English, 12)?.to_string(),
};

let p = db.insert_new_profile(new_profile)?;

Ok(mnemonic)
info!("creating new seed");
Ok(Mnemonic::from_str(&p.seed_words)?)
}
}
}
25 changes: 19 additions & 6 deletions src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,17 @@ use iced::{
use log::error;
use tokio::time::sleep;

use crate::fedimint_client::{
select_gateway, spawn_internal_payment_subscription, spawn_invoice_payment_subscription,
spawn_invoice_receive_subscription, FedimintClient,
};
use crate::{
bridge::{self, CoreUIMsg, UICoreMsg},
conf, Message,
conf::{self, get_mnemonic},
Message,
};
use crate::{
db::setup_db,
fedimint_client::{
select_gateway, spawn_internal_payment_subscription, spawn_invoice_payment_subscription,
spawn_invoice_receive_subscription, FedimintClient,
},
};

struct HarborCore {
Expand Down Expand Up @@ -175,7 +179,16 @@ pub fn run_core() -> Subscription<Message> {
let path = PathBuf::from(&conf::data_dir(network));
std::fs::create_dir_all(path.clone()).expect("Could not create datadir");

let mnemonic = conf::get_mnemonic(network).expect("Could not get mnemonic");
// Create or get the database
// FIXME: pass in password
let db = setup_db(
path.join("harbor.sqlite")
.to_str()
.expect("path must be correct"),
"password123".to_string(),
);

let mnemonic = get_mnemonic(db).expect("should get seed");

// fixme, properly initialize this
let client = FedimintClient::new(
Expand Down
87 changes: 87 additions & 0 deletions src/db.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
use crate::db_models::{NewProfile, Profile};
use diesel::{
connection::SimpleConnection,
r2d2::{ConnectionManager, Pool},
SqliteConnection,
};
use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
use std::{sync::Arc, time::Duration};

pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!();

pub(crate) fn setup_db(url: &str, password: String) -> Arc<dyn DBConnection + Send + Sync> {
let manager = ConnectionManager::<SqliteConnection>::new(url);
let pool = Pool::builder()
.max_size(50)
.connection_customizer(Box::new(ConnectionOptions {
key: password,
enable_wal: true,
enable_foreign_keys: true,
busy_timeout: Some(Duration::from_secs(15)),
}))
.test_on_check_out(true)
.build(manager)
.expect("Unable to build DB connection pool");
Arc::new(SQLConnection { db: pool })
}

pub trait DBConnection {
// Gets a seed from the first profile in the DB or returns None
fn get_seed(&self) -> anyhow::Result<Option<String>>;

// Inserts a new profile into the DB
fn insert_new_profile(&self, new_profile: NewProfile) -> anyhow::Result<Profile>;
}

pub(crate) struct SQLConnection {
db: Pool<ConnectionManager<SqliteConnection>>,
}

impl DBConnection for SQLConnection {
fn get_seed(&self) -> anyhow::Result<Option<String>> {
let conn = &mut self.db.get()?;
match Profile::get_first(conn)? {
Some(p) => Ok(Some(p.seed_words)),
None => Ok(None),
}
}

fn insert_new_profile(&self, new_profile: NewProfile) -> anyhow::Result<Profile> {
let conn = &mut self.db.get()?;
new_profile.insert(conn)
}
}

#[derive(Debug)]
pub struct ConnectionOptions {
pub key: String,
pub enable_wal: bool,
pub enable_foreign_keys: bool,
pub busy_timeout: Option<Duration>,
}

impl diesel::r2d2::CustomizeConnection<SqliteConnection, diesel::r2d2::Error>
for ConnectionOptions
{
fn on_acquire(&self, conn: &mut SqliteConnection) -> Result<(), diesel::r2d2::Error> {
(|| {
// FIXME: Special characters might fuck up
conn.batch_execute(&format!("PRAGMA key={}", self.key))?;
if self.enable_wal {
conn.batch_execute("PRAGMA journal_mode = WAL; PRAGMA synchronous = NORMAL;")?;
}
if self.enable_foreign_keys {
conn.batch_execute("PRAGMA foreign_keys = ON;")?;
}
if let Some(d) = self.busy_timeout {
conn.batch_execute(&format!("PRAGMA busy_timeout = {};", d.as_millis()))?;
}

conn.run_pending_migrations(MIGRATIONS)
.expect("Migration has to run successfully");

Ok(())
})()
.map_err(diesel::r2d2::Error::QueryError)
}
}
4 changes: 4 additions & 0 deletions src/db_models/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub mod profile;
pub use profile::*;

pub mod schema;
44 changes: 44 additions & 0 deletions src/db_models/profile.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use crate::db_models::schema::profile;
use diesel::prelude::*;
use serde::{Deserialize, Serialize};

#[derive(
QueryableByName, Queryable, AsChangeset, Serialize, Deserialize, Debug, Clone, PartialEq,
)]
#[diesel(table_name = profile)]
pub struct Profile {
pub id: String,
pub seed_words: String,
}

impl Profile {
pub fn get_first(conn: &mut SqliteConnection) -> anyhow::Result<Option<Profile>> {
Ok(profile::table.first::<Profile>(conn).optional()?)
}
}

#[derive(Insertable)]
#[diesel(table_name = profile)]
pub struct NewProfile {
pub id: String,
pub seed_words: String,
}

impl From<&NewProfile> for Profile {
fn from(new_profile: &NewProfile) -> Self {
Profile {
id: new_profile.id.clone(),
seed_words: new_profile.seed_words.clone(),
}
}
}

impl NewProfile {
pub fn insert(&self, conn: &mut SqliteConnection) -> anyhow::Result<Profile> {
let _ = diesel::insert_into(profile::table)
.values(self)
.execute(conn)?;

Ok(self.into())
}
}
8 changes: 8 additions & 0 deletions src/db_models/schema.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// @generated automatically by Diesel CLI.

diesel::table! {
profile (id) {
id -> Text,
seed_words -> Text,
}
}
2 changes: 2 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ pub mod bridge;
pub mod components;
pub mod conf;
pub mod core;
pub mod db;
pub mod db_models;
mod fedimint_client;
pub mod routes;

Expand Down