Skip to content

Commit

Permalink
Merge pull request #10 from MutinyWallet/fedimint-refactor
Browse files Browse the repository at this point in the history
Beginnings of multi federation
  • Loading branch information
TonyGiorgio authored May 14, 2024
2 parents 9e7fc54 + c16558a commit 8643d13
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 54 deletions.
3 changes: 3 additions & 0 deletions src/bridge.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use fedimint_core::Amount;
use fedimint_core::api::InviteCode;
use fedimint_ln_common::lightning_invoice::Bolt11Invoice;
use tokio::sync::mpsc;

Expand All @@ -8,6 +9,7 @@ pub enum UICoreMsg {
FakeSend(u64),
Send(Bolt11Invoice),
Receive(u64),
AddFederation(InviteCode),
}

#[derive(Debug, Clone)]
Expand All @@ -20,6 +22,7 @@ pub enum CoreUIMsg {
ReceiveSuccess,
ReceiveFailed(String),
BalanceUpdated(Amount),
AddFederationFailed(String),
}

#[derive(Debug)]
Expand Down
103 changes: 65 additions & 38 deletions src/core.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use anyhow::anyhow;
use bip39::Mnemonic;
use bitcoin::Network;
use fedimint_core::api::InviteCode;
use fedimint_core::config::FederationId;
use fedimint_core::Amount;
use fedimint_ln_client::{LightningClientModule, PayType};
use fedimint_ln_common::lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, Description};
use std::collections::HashMap;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::atomic::AtomicBool;
Expand All @@ -14,6 +17,7 @@ use iced::{
subscription::{self, Subscription},
};
use log::error;
use tokio::sync::RwLock;
use tokio::time::sleep;

use crate::{
Expand All @@ -31,8 +35,11 @@ use crate::{

struct HarborCore {
balance: Amount,
network: Network,
mnemonic: Mnemonic,
tx: Sender<Message>,
client: FedimintClient, // todo multiple clients
clients: Arc<RwLock<HashMap<FederationId, FedimintClient>>>,
stop: Arc<AtomicBool>,
}

impl HarborCore {
Expand Down Expand Up @@ -77,14 +84,17 @@ impl HarborCore {
self.msg(CoreUIMsg::BalanceUpdated(self.balance)).await;
}

async fn send(&mut self, invoice: Bolt11Invoice) -> anyhow::Result<()> {
log::info!("Sending invoice: {invoice}");
let lightning_module = self
.client
.fedimint_client
.get_first_module::<LightningClientModule>();
// todo for now just use the first client, but eventually we'll want to have a way to select a client
async fn get_client(&self) -> FedimintClient {
self.clients.read().await.values().next().unwrap().clone()
}

async fn send(&self, invoice: Bolt11Invoice) -> anyhow::Result<()> {
// todo go through all clients and select the first one that has enough balance
let client = self.get_client().await.fedimint_client;
let lightning_module = client.get_first_module::<LightningClientModule>();

let gateway = select_gateway(&self.client.fedimint_client)
let gateway = select_gateway(&client)
.await
.ok_or(anyhow!("Internal error: No gateway found for federation"))?;

Expand All @@ -95,21 +105,11 @@ impl HarborCore {
match outgoing.payment_type {
PayType::Internal(op_id) => {
let sub = lightning_module.subscribe_internal_pay(op_id).await?;
spawn_internal_payment_subscription(
self.tx.clone(),
self.client.fedimint_client.clone(),
sub,
)
.await;
spawn_internal_payment_subscription(self.tx.clone(), client.clone(), sub).await;
}
PayType::Lightning(op_id) => {
let sub = lightning_module.subscribe_ln_pay(op_id).await?;
spawn_invoice_payment_subscription(
self.tx.clone(),
self.client.fedimint_client.clone(),
sub,
)
.await;
spawn_invoice_payment_subscription(self.tx.clone(), client.clone(), sub).await;
}
}

Expand All @@ -119,12 +119,10 @@ impl HarborCore {
}

async fn receive(&self, amount: u64) -> anyhow::Result<Bolt11Invoice> {
let lightning_module = self
.client
.fedimint_client
.get_first_module::<LightningClientModule>();
let client = self.get_client().await.fedimint_client;
let lightning_module = client.get_first_module::<LightningClientModule>();

let gateway = select_gateway(&self.client.fedimint_client)
let gateway = select_gateway(&client)
.await
.ok_or(anyhow!("Internal error: No gateway found for federation"))?;

Expand All @@ -143,18 +141,32 @@ impl HarborCore {

// Create subscription to operation if it exists
if let Ok(subscription) = lightning_module.subscribe_ln_receive(op_id).await {
spawn_invoice_receive_subscription(
self.tx.clone(),
self.client.fedimint_client.clone(),
subscription,
)
.await;
spawn_invoice_receive_subscription(self.tx.clone(), client.clone(), subscription).await;
} else {
error!("Could not create subscription to lightning receive");
}

Ok(invoice)
}

async fn add_federation(&self, invite_code: InviteCode) -> anyhow::Result<()> {
let id = invite_code.federation_id();

let mut clients = self.clients.write().await;
if clients.get(&id).is_some() {
return Err(anyhow!("Federation already added"));
}

let client =
FedimintClient::new(invite_code, &self.mnemonic, self.network, self.stop.clone())
.await?;

clients.insert(client.fedimint_client.federation_id(), client);

// todo add to database

Ok(())
}
}

pub fn run_core() -> Subscription<Message> {
Expand All @@ -169,8 +181,7 @@ pub fn run_core() -> Subscription<Message> {
}
let mut state = State::NeedsInit;

let handles = bridge::create_handles();
let (ui_handle, mut core_handle) = handles;
let (ui_handle, mut core_handle) = bridge::create_handles();
let arc_ui_handle = Arc::new(ui_handle);

let network = Network::Signet;
Expand All @@ -190,24 +201,33 @@ pub fn run_core() -> Subscription<Message> {

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

let stop = Arc::new(AtomicBool::new(false));

// fixme, properly initialize this
let client = FedimintClient::new(
"test".to_string(),
InviteCode::from_str("fed11qgqzc2nhwden5te0vejkg6tdd9h8gepwvejkg6tdd9h8garhduhx6at5d9h8jmn9wshxxmmd9uqqzgxg6s3evnr6m9zdxr6hxkdkukexpcs3mn7mj3g5pc5dfh63l4tj6g9zk4er").unwrap(),
&mnemonic,
network,
Arc::new(AtomicBool::new(false)),
stop.clone(),
)
.await
.expect("Could not create fedimint client");

let balance = client.fedimint_client.get_balance().await;
let mut clients = HashMap::new();
clients.insert(client.fedimint_client.federation_id(), client);

let mut balance = Amount::ZERO;
for client in clients.values() {
balance += client.fedimint_client.get_balance().await;
}

let mut core = HarborCore {
balance,
tx,
// TODO: add a database handle that works across async stuff
client,
mnemonic,
network,
clients: Arc::new(RwLock::new(clients)),
stop,
};

loop {
Expand Down Expand Up @@ -255,6 +275,13 @@ pub fn run_core() -> Subscription<Message> {
}
}
}
UICoreMsg::AddFederation(invite_code) => {
if let Err(e) = core.add_federation(invite_code).await {
error!("Error adding federation: {e}");
core.msg(CoreUIMsg::AddFederationFailed(e.to_string()))
.await;
}
}
}
}
}
Expand Down
42 changes: 26 additions & 16 deletions src/fedimint_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,32 +19,28 @@ use fedimint_wallet_client::{WalletClientInit, WalletClientModule};
use iced::futures::channel::mpsc::Sender;
use iced::futures::{SinkExt, StreamExt};
use log::{debug, error, info, trace};
use std::sync::atomic::AtomicBool;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Instant;
use tokio::spawn;

#[derive(Debug, Clone)]
#[allow(unused)] // TODO: remove
pub(crate) struct FedimintClient {
pub(crate) uuid: String,
pub(crate) fedimint_client: ClientHandleArc,
invite_code: InviteCode,
stop: Arc<AtomicBool>,
}

impl FedimintClient {
#[allow(clippy::too_many_arguments)]
pub(crate) async fn new(
uuid: String,
federation_code: InviteCode,
invite_code: InviteCode,
mnemonic: &Mnemonic,
network: Network,
stop: Arc<AtomicBool>,
) -> anyhow::Result<Self> {
info!("initializing a new federation client: {uuid}");

let federation_id = federation_code.federation_id();
let federation_id = invite_code.federation_id();
info!("initializing a new federation client: {federation_id}");

trace!("Building fedimint client db");
// todo use a real db
Expand Down Expand Up @@ -72,7 +68,7 @@ impl FedimintClient {
})?
} else {
let download = Instant::now();
let config = ClientConfig::download_from_invite_code(&federation_code)
let config = ClientConfig::download_from_invite_code(&invite_code)
.await
.map_err(|e| {
error!("Could not download federation info: {e}");
Expand Down Expand Up @@ -109,6 +105,7 @@ impl FedimintClient {

// Update gateway cache in background
let client_clone = fedimint_client.clone();
let stop_clone = stop.clone();
spawn(async move {
let start = Instant::now();
let lightning_module = client_clone.get_first_module::<LightningClientModule>();
Expand All @@ -123,26 +120,36 @@ impl FedimintClient {
}

trace!(
"Setting active gateway took: {}ms",
"Updating gateway cache took: {}ms",
start.elapsed().as_millis()
);

// continually update gateway cache
loop {
lightning_module
.update_gateway_cache_continuously(|g| async { g })
.await;
if stop_clone.load(Ordering::Relaxed) {
break;
}
}
});

debug!("Built fedimint client");

Ok(FedimintClient {
uuid,
fedimint_client,
invite_code: federation_code,
invite_code,
stop,
})
}
}

pub(crate) async fn select_gateway(client: &ClientHandleArc) -> Option<LightningGateway> {
let ln = client.get_first_module::<LightningClientModule>();
let mut selected_gateway = None;
for gateway in ln.list_gateways().await {
let gateways = ln.list_gateways().await;
let mut selected_gateway: Option<LightningGateway> = None;
for gateway in gateways.iter() {
// first try to find a vetted gateway
if gateway.vetted {
// if we can select the gateway, return it
Expand All @@ -155,14 +162,17 @@ pub(crate) async fn select_gateway(client: &ClientHandleArc) -> Option<Lightning
let fees = gateway.info.fees;
if fees.base_msat >= 1_000 && fees.proportional_millionths >= 100 {
if let Some(g) = ln.select_gateway(&gateway.info.gateway_id).await {
selected_gateway = Some(g);
// only select gateways that support private payments, unless we don't have a gateway
if g.supports_private_payments || selected_gateway.is_none() {
selected_gateway = Some(g);
}
}
}
}

// if no gateway found, just select the first one we can find
if selected_gateway.is_none() {
for gateway in ln.list_gateways().await {
for gateway in gateways {
if let Some(g) = ln.select_gateway(&gateway.info.gateway_id).await {
selected_gateway = Some(g);
break;
Expand Down
4 changes: 4 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,10 @@ impl HarborWallet {
self.receive_invoice = Some(invoice);
Command::none()
}
CoreUIMsg::AddFederationFailed(_) => {
// todo show error
Command::none()
}
},
}
}
Expand Down

0 comments on commit 8643d13

Please sign in to comment.