Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Commit

Permalink
hold: multimint support via federation Id map
Browse files Browse the repository at this point in the history
  • Loading branch information
Kodylow committed Jan 10, 2024
1 parent 6aad281 commit 4cb1323
Show file tree
Hide file tree
Showing 9 changed files with 119 additions and 28 deletions.
13 changes: 7 additions & 6 deletions migrations/20231224194609_create_tables.sql
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ CREATE TABLE app_user (
id SERIAL PRIMARY KEY,
pubkey VARCHAR(64) NOT NULL,
name VARCHAR(20) NOT NULL,
dm_type VARCHAR(5) NOT NULL
dm_type VARCHAR(5) NOT NULL,
federation_id VARCHAR(64) NOT NULL
);
CREATE TABLE relay (
id SERIAL PRIMARY KEY,
Expand All @@ -16,15 +17,15 @@ CREATE TABLE app_user_relays (
);
CREATE TABLE invoice (
id SERIAL PRIMARY KEY,
federation_id VARCHAR(64) NOT NULL,
op_id VARCHAR(64) NOT NULL,
app_user_id INTEGER NOT NULL references app_user(id),
bolt11 VARCHAR(2048) NOT NULL,
amount BIGINT NOT NULL,
state INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE zaps
(
id INTEGER NOT NULL PRIMARY KEY references invoice (id),
request TEXT NOT NULL,
CREATE TABLE zaps (
id INTEGER NOT NULL PRIMARY KEY references invoice (id),
request TEXT NOT NULL,
event_id VARCHAR(64)
);
);
19 changes: 12 additions & 7 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ async fn main() -> Result<()> {
tracing_subscriber::fmt::init();

let state = AppState {
fm: load_fedimint_client().await?,
fm_clients: load_existing_clients().await,
mm: ModelManager::new().await?,
nostr: load_nostr_client().await?,
};
Expand All @@ -48,8 +48,6 @@ async fn main() -> Result<()> {
/// Starts subscription for all pending invoices from previous run
async fn handle_pending_invoices(state: AppState) -> Result<()> {
let invoices = InvoiceBmc::get_pending(&state.mm).await?;
let ln = state.fm.get_first_module::<LightningClientModule>();

// sort invoices by user for efficiency
let invoices_by_user = invoices
.into_iter()
Expand All @@ -59,22 +57,29 @@ async fn handle_pending_invoices(state: AppState) -> Result<()> {
.map(|(user, invs)| (user, invs.collect::<Vec<_>>()))
.collect::<Vec<_>>();

for (user, invoices) in invoices_by_user {
let nip05relays = AppUserRelaysBmc::get_by_id(&state.mm, user).await?;
for invoice in invoices {
// create subscription to operation if it exists
let fm_clients = state.fm_clients.lock().await;
for invoice in invoices {
let nip05relays = AppUserRelaysBmc::get_by_id(&state.mm, invoice.app_user_id).await?;
if let Some(client) = fm_clients.get(&nip05relays.federation_id) {
let ln = client.get_first_module::<LightningClientModule>();
if let Ok(subscription) = ln
.subscribe_ln_receive(invoice.op_id.parse().expect("invalid op_id"))
.await
{
spawn_invoice_subscription(
state.clone(),
nip05relays.federation_id,
invoice.id,
nip05relays.clone(),
subscription,
)
.await;
}
} else {
error!(
"No client found for federation_id: {}",
nip05relays.federation_id
);
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/model/app_user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,23 @@ pub struct AppUser {
pub pubkey: String,
pub name: String,
pub dm_type: String,
pub federation_id: String,
}

#[derive(Debug, Clone, Fields, FromRow, Serialize)]
pub struct AppUserForCreate {
pub pubkey: String,
pub name: String,
pub dm_type: String,
pub federation_id: String,
}

#[derive(Debug, Clone, Fields, FromRow, Serialize)]
pub struct AppUserForUpdate {
pub pubkey: Option<String>,
pub name: Option<String>,
pub dm_type: Option<String>,
pub federation_id: Option<String>,
}

pub struct AppUserBmc;
Expand Down
4 changes: 4 additions & 0 deletions src/model/app_user_relays.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub struct AppUserRelaysForCreate {
pub name: String,
pub dm_type: String,
pub relays: Vec<String>,
pub federation_id: String,
}

#[derive(Debug, Clone, FromRow, Serialize)]
Expand All @@ -51,6 +52,7 @@ impl AppUserRelaysBmc {
pubkey: app_user_relays_c.pubkey,
name: app_user_relays_c.name,
dm_type: app_user_relays_c.dm_type,
federation_id: app_user_relays_c.federation_id,
};
let user_id = base::create::<Self, _>(mm, user_c).await?;

Expand Down Expand Up @@ -88,6 +90,7 @@ impl AppUserRelaysBmc {

let userrelays = AppUserRelays {
app_user_id: user.id,
federation_id: user.federation_id,
pubkey: user.pubkey,
name: user.name,
dm_type: user.dm_type,
Expand Down Expand Up @@ -124,6 +127,7 @@ impl AppUserRelaysBmc {

let userrelays = AppUserRelays {
app_user_id: user.id,
federation_id: user.federation_id,
pubkey: user.pubkey,
name: user.name,
dm_type: user.dm_type,
Expand Down
1 change: 1 addition & 0 deletions src/model/invoice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub struct Invoice {
#[derive(Debug, Clone, Fields, FromRow, Serialize)]
pub struct InvoiceForCreate {
pub op_id: String,
pub federation_id: String,
pub app_user_id: i32,
pub bolt11: String,
pub amount: i64,
Expand Down
39 changes: 32 additions & 7 deletions src/router/handlers/lnurlp/callback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use axum::{
Json,
};
use fedimint_client::oplog::UpdateStreamOrOutcome;
use fedimint_core::{core::OperationId, task::spawn, Amount};
use fedimint_core::{config::FederationId, core::OperationId, task::spawn, Amount};
use fedimint_ln_client::{LightningClientModule, LnReceiveState};
use fedimint_mint_client::{MintClientModule, OOBNotes};
use futures::StreamExt;
Expand Down Expand Up @@ -105,8 +105,12 @@ pub async fn handle_callback(
}

let nip05relays = AppUserRelaysBmc::get_by(&state.mm, NameOrPubkey::Name, &username).await?;

let ln = state.fm.get_first_module::<LightningClientModule>();
let fm_client: ClientArc = state
.fm_clients
.lock()
.await
.get(FederationId::from_str(&nip05relays.federation_id))?;
let ln = fm_client.get_first_module::<LightningClientModule>();
let (op_id, pr) = ln
.create_bolt11_invoice(
Amount {
Expand All @@ -123,6 +127,7 @@ pub async fn handle_callback(
&state.mm,
InvoiceForCreate {
op_id: op_id.to_string(),
federation_id: nip05relays.federation_id,
app_user_id: nip05relays.app_user_id,
amount: params.amount as i64,
bolt11: pr.to_string(),
Expand All @@ -149,7 +154,14 @@ pub async fn handle_callback(
.await
.expect("subscribing to a just created operation can't fail");

spawn_invoice_subscription(state, id, nip05relays, subscription).await;
spawn_invoice_subscription(
state,
FederationId::from_str(&nip05relays.federation_id),
id,
nip05relays,
subscription,
)
.await;

let verify_url = format!(
"http://{}:{}/lnurlp/{}/verify/{}",
Expand All @@ -170,6 +182,7 @@ pub async fn handle_callback(

pub(crate) async fn spawn_invoice_subscription(
state: AppState,
federation_id: FederationId,
id: i32,
userrelays: AppUserRelays,
subscription: UpdateStreamOrOutcome<LnReceiveState>,
Expand All @@ -190,9 +203,15 @@ pub(crate) async fn spawn_invoice_subscription(
let invoice = InvoiceBmc::set_state(&state.mm, id, InvoiceState::Settled)
.await
.expect("settling invoice can't fail");
notify_user(&state, id, invoice.amount as u64, userrelays.clone())
.await
.expect("notifying user can't fail");
notify_user(
&state,
federation_id,
id,
invoice.amount as u64,
userrelays.clone(),
)
.await
.expect("notifying user can't fail");
break;
}
_ => {}
Expand All @@ -203,10 +222,16 @@ pub(crate) async fn spawn_invoice_subscription(

async fn notify_user(
state: &AppState,
federation_id: FederationId,
id: i32,
amount: u64,
app_user_relays: AppUserRelays,
) -> Result<(), Box<dyn std::error::Error>> {
let fm_client: ClientArc = state
.fm_clients
.lock()
.await
.get(FederationId::from_str(&federation_id))?;
let mint = state.fm.get_first_module::<MintClientModule>();
let (operation_id, notes) = mint
.spend_notes(Amount::from_msats(amount), Duration::from_secs(604800), ())
Expand Down
1 change: 1 addition & 0 deletions src/router/handlers/nostr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub mod well_known;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppUserRelays {
pub app_user_id: i32,
pub federation_id: String,
pub pubkey: String,
pub name: String,
pub dm_type: String,
Expand Down
2 changes: 2 additions & 0 deletions src/router/handlers/nostr/register.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub struct UserParams {
pub name: String,
pub dm_type: SupportedDmType,
pub relays: Option<Vec<String>>,
pub federation_id: FederationId,
}

#[axum_macros::debug_handler]
Expand Down Expand Up @@ -50,6 +51,7 @@ pub async fn handle_register(
name: params.name,
dm_type: params.dm_type.to_string(),
relays,
federation_id: params.federation_id.to_string(),
};

match AppUserRelaysBmc::register(&state.mm, nip05relays_c).await {
Expand Down
65 changes: 57 additions & 8 deletions src/state.rs
Original file line number Diff line number Diff line change
@@ -1,43 +1,92 @@
use std::{collections::BTreeMap, path::PathBuf};

use fedimint_client::ClientArc;
use nostr_sdk::Client;

use crate::{config, model::ModelManager};

use anyhow::Result;
use config::CONFIG;
use fedimint_client::{get_config_from_db, FederationInfo};
use fedimint_core::db::Database;
use fedimint_ln_client::LightningClientInit;
use fedimint_mint_client::MintClientInit;
use fedimint_wallet_client::WalletClientInit;

#[derive(Clone)]
pub struct AppState {
pub fm: ClientArc,
pub fm_clients: Arc<Mutex<BTreeMap<FederationId, ClientArc>>>,
pub mm: ModelManager,
pub nostr: Client,
}

pub async fn load_fedimint_client() -> Result<ClientArc> {
pub async fn load_existing_clients() -> Arc<Mutex<BTreeMap<FederationId, ClientArc>>> {
let mut clients = BTreeMap::new();
let fm_dbs = CONFIG
.fm_db_path
.read_dir()
.expect("Failed to read fm db path")
.flatten();
for db in fm_dbs {
let client_db_path = db.path();
let client = load_fedimint_client(client_db_path)
.await
.expect("Failed to load client");
let federation_id = client.federation_id();
clients.insert(federation_id, client);
}

Arc::new(Mutex::new(clients))
}

pub async fn load_fedimint_client(file_path: PathBuf) -> Result<ClientArc> {
let fedimint_client_path = CONFIG.fm_db_path.join(file_path);

let db = Database::new(
fedimint_rocksdb::RocksDb::open(CONFIG.fm_db_path.clone())?,
Default::default(),
);
let mut client_builder = fedimint_client::Client::builder();
if get_config_from_db(&db).await.is_none() {
let federation_info = FederationInfo::from_invite_code(CONFIG.invite_code.clone()).await?;
client_builder.with_federation_info(federation_info);
};

client_builder.with_database(db);
client_builder.with_module(WalletClientInit(None));
client_builder.with_module(MintClientInit);
client_builder.with_module(LightningClientInit);
client_builder.with_primary_module(1);
let client_res = client_builder.build(CONFIG.root_secret.clone()).await?;
let client_secret = match client_builder.load_decodable_client_secret().await {
Ok(secret) => secret,
Err(_) => {
let secret = PlainRootSecretStrategy::random(&mut thread_rng());
store_encodable_client_secret(client_builder.db(), secret)
.await
.map_err(|e| anyhow!("Failed to store client secret: {}", e))?;
secret
}
};
let root_secret = PlainRootSecretStrategy::to_root_secret(&client_secret);
let client_res = client_builder.build(root_secret).await?;

Ok(client_res)
}

pub async fn store_encodable_client_secret<T: Encodable>(
db: &Database,
secret: T,
) -> anyhow::Result<()> {
let mut dbtx = db.begin_transaction().await;

// Don't overwrite an existing secret
match dbtx.get_value(&EncodedClientSecretKey).await {
Some(_) => bail!("Encoded client secret already exists, cannot overwrite"),
None => {
let encoded_secret = consensus_encode_to_vec(&secret);
dbtx.insert_entry(&EncodedClientSecretKey, &encoded_secret)
.await;
dbtx.commit_tx().await;
Ok(())
}
}
}

pub async fn load_nostr_client() -> Result<Client> {
let client = nostr_sdk::Client::new(&CONFIG.nostr_sk);

Expand Down

0 comments on commit 4cb1323

Please sign in to comment.