Skip to content
Draft
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
41 changes: 41 additions & 0 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion fedimint-testing/src/gateway.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use fedimint_logging::LOG_TEST;
use futures::executor::block_on;
use lightning_invoice::RoutingFees;
use ln_gateway::client::GatewayClientBuilder;
use ln_gateway::lnrpc_client::{ILnRpcClient, LightningBuilder};
use ln_gateway::lightning::{ILnRpcClient, LightningBuilder};
use ln_gateway::rpc::rpc_client::GatewayRpcClient;
use ln_gateway::rpc::{ConnectFedPayload, FederationConnectionInfo};
use ln_gateway::{Gateway, GatewayState};
Expand Down
3 changes: 2 additions & 1 deletion fedimint-testing/src/ln/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ use ln_gateway::gateway_lnrpc::{
self, EmptyResponse, GetNodeInfoResponse, GetRouteHintsResponse, InterceptHtlcResponse,
PayInvoiceRequest, PayInvoiceResponse,
};
use ln_gateway::lnrpc_client::{HtlcResult, ILnRpcClient, LightningRpcError, RouteHtlcStream};
use ln_gateway::lightning::cln::{HtlcResult, RouteHtlcStream};
use ln_gateway::lightning::{ILnRpcClient, LightningRpcError};
use rand::rngs::OsRng;
use tokio::sync::mpsc;
use tracing::info;
Expand Down
2 changes: 1 addition & 1 deletion fedimint-testing/src/ln/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use fedimint_core::{Amount, BitcoinHash};
use lightning_invoice::{
Bolt11Invoice, Currency, InvoiceBuilder, PaymentSecret, DEFAULT_EXPIRY_TIME,
};
use ln_gateway::lnrpc_client::ILnRpcClient;
use ln_gateway::lightning::ILnRpcClient;
use rand::rngs::OsRng;
use secp256k1_zkp::SecretKey;

Expand Down
7 changes: 3 additions & 4 deletions fedimint-testing/src/ln/real.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,9 @@ use ln_gateway::gateway_lnrpc::{
EmptyResponse, GetNodeInfoResponse, GetRouteHintsResponse, InterceptHtlcResponse,
PayInvoiceRequest, PayInvoiceResponse,
};
use ln_gateway::lnd::GatewayLndClient;
use ln_gateway::lnrpc_client::{
ILnRpcClient, LightningRpcError, NetworkLnRpcClient, RouteHtlcStream,
};
use ln_gateway::lightning::cln::{NetworkLnRpcClient, RouteHtlcStream};
use ln_gateway::lightning::lnd::GatewayLndClient;
use ln_gateway::lightning::{ILnRpcClient, LightningRpcError};
use secp256k1::PublicKey;
use tokio::sync::Mutex;
use tonic_lnd::lnrpc::{GetInfoRequest, Invoice as LndInvoice, ListChannelsRequest};
Expand Down
1 change: 1 addition & 0 deletions gateway/ln-gateway/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ tonic_lnd = { workspace = true }
tower-http = { version = "0.4.3", features = ["cors", "auth"] }
tracing = { version = "0.1.37", default-features = false, features= ["log", "attributes", "std"] }
url = { version = "2.3.1", features = ["serde"] }
zebedee-rust = "0.6.0"

[dev-dependencies]
fedimint-dummy-server = { path = "../../modules/fedimint-dummy-server" }
Expand Down
2 changes: 1 addition & 1 deletion gateway/ln-gateway/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use rand::thread_rng;
use tracing::info;

use crate::db::{FederationConfig, FederationIdKey, FederationIdKeyPrefix};
use crate::lnrpc_client::ILnRpcClient;
use crate::lightning::ILnRpcClient;
use crate::state_machine::GatewayClientInit;
use crate::{FederationToClientMap, GatewayError, Result, ScidToFederationMap};

Expand Down
34 changes: 5 additions & 29 deletions gateway/ln-gateway/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
pub mod client;
pub mod db;
pub mod lnd;
pub mod lnrpc_client;
pub mod lightning;
pub mod rpc;
pub mod state_machine;
pub mod types;
Expand All @@ -26,7 +25,7 @@ use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use bitcoin::{Address, Network, Txid};
use bitcoin_hashes::hex::ToHex;
use clap::{Parser, Subcommand};
use clap::Parser;
use client::GatewayClientBuilder;
use db::{
DbKeyPrefix, FederationIdKey, GatewayConfiguration, GatewayConfigurationKey, GatewayPublicKey,
Expand Down Expand Up @@ -58,15 +57,14 @@ use fedimint_wallet_client::{
use futures::stream::StreamExt;
use gateway_lnrpc::intercept_htlc_response::Action;
use gateway_lnrpc::{GetNodeInfoResponse, InterceptHtlcResponse};
use lightning::{ILnRpcClient, LightningBuilder, LightningMode, LightningRpcError};
use lightning_invoice::RoutingFees;
use lnrpc_client::{ILnRpcClient, LightningBuilder, LightningRpcError, RouteHtlcStream};
use rand::rngs::OsRng;
use rpc::{
FederationConnectionInfo, FederationInfo, GatewayFedConfig, GatewayInfo, LeaveFedPayload,
SetConfigurationPayload,
};
use secp256k1::PublicKey;
use serde::{Deserialize, Serialize};
use state_machine::pay::OutgoingPaymentError;
use state_machine::GatewayClientModule;
use strum::IntoEnumIterator;
Expand All @@ -76,7 +74,8 @@ use tracing::{debug, error, info, warn};

use crate::db::{FederationConfig, FederationIdKeyPrefix};
use crate::gateway_lnrpc::intercept_htlc_response::Forward;
use crate::lnrpc_client::GatewayLightningBuilder;
use crate::lightning::cln::RouteHtlcStream;
use crate::lightning::GatewayLightningBuilder;
use crate::rpc::rpc_server::run_webserver;
use crate::rpc::{
BackupPayload, BalancePayload, ConnectFedPayload, DepositAddressPayload, RestorePayload,
Expand Down Expand Up @@ -1388,29 +1387,6 @@ async fn wait_for_new_password(
.await;
}

#[derive(Debug, Clone, Subcommand, Serialize, Deserialize)]
pub enum LightningMode {
#[clap(name = "lnd")]
Lnd {
/// LND RPC address
#[arg(long = "lnd-rpc-host", env = "FM_LND_RPC_ADDR")]
lnd_rpc_addr: String,

/// LND TLS cert file path
#[arg(long = "lnd-tls-cert", env = "FM_LND_TLS_CERT")]
lnd_tls_cert: String,

/// LND macaroon file path
#[arg(long = "lnd-macaroon", env = "FM_LND_MACAROON")]
lnd_macaroon: String,
},
#[clap(name = "cln")]
Cln {
#[arg(long = "cln-extension-addr", env = "FM_GATEWAY_LIGHTNING_ADDR")]
cln_extension_addr: SafeUrl,
},
}

#[derive(Debug, Error)]
pub enum GatewayError {
#[error("Federation error: {}", OptStacktrace(.0))]
Expand Down
179 changes: 179 additions & 0 deletions gateway/ln-gateway/src/lightning/alby.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
use std::collections::BTreeMap;
use std::fmt;
use std::net::SocketAddr;
use std::str::FromStr;
use std::sync::Arc;

use async_trait::async_trait;
use fedimint_core::task::TaskGroup;
use fedimint_core::Amount;
use fedimint_ln_common::PrunedInvoice;
use secp256k1::PublicKey;
use serde::{Deserialize, Serialize};
use serde_json::json;
use tokio::sync::oneshot::Sender;
use tokio::sync::{mpsc, Mutex};
use tokio_stream::wrappers::ReceiverStream;
use tracing::info;

use super::{
send_htlc_to_webhook, ILnRpcClient, LightningRpcError, RouteHtlcStream, WebhookClient,
};
use crate::gateway_lnrpc::{
EmptyResponse, GetNodeInfoResponse, GetRouteHintsResponse, InterceptHtlcRequest,
InterceptHtlcResponse, PayInvoiceRequest, PayInvoiceResponse,
};
use crate::rpc::rpc_webhook_server::run_webhook_server;

#[derive(Debug, Clone, Deserialize, Serialize)]
struct AlbyPayResponse {
amount: u64,
description: String,
destination: String,
fee: u64,
payment_hash: Vec<u8>,
payment_preimage: Vec<u8>,
payment_request: String,
}

#[derive(Clone)]
pub struct GatewayAlbyClient {
bind_addr: SocketAddr,
api_key: String,
pub outcomes: Arc<Mutex<BTreeMap<u64, Sender<InterceptHtlcResponse>>>>,
}

impl GatewayAlbyClient {
pub async fn new(
bind_addr: SocketAddr,
api_key: String,
outcomes: Arc<Mutex<BTreeMap<u64, Sender<InterceptHtlcResponse>>>>,
) -> Self {
info!("Gateway configured to connect to Alby at \n address: {bind_addr:?}");
Self {
api_key,
bind_addr,
outcomes,
}
}
}

impl fmt::Debug for GatewayAlbyClient {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "AlbyClient")
}
}

#[async_trait]
impl ILnRpcClient for GatewayAlbyClient {
/// Returns the public key of the lightning node to use in route hint
///
/// What should we do here with Alby?
/// - Is the pubkey always the same?
/// - Could change to optional: If Custodial API, hardcode the pubkey for
/// the node
async fn info(&self) -> Result<GetNodeInfoResponse, LightningRpcError> {
let mainnet = "mainnet";
let alias = "getalby.com";
let pub_key = PublicKey::from_str(
"030a58b8653d32b99200a2334cfe913e51dc7d155aa0116c176657a4f1722677a3",
)
.unwrap();
let pub_key = pub_key.serialize().to_vec();

return Ok(GetNodeInfoResponse {
pub_key,
alias: alias.to_string(),
network: mainnet.to_string(),
});
}

/// We can probably just use the Alby node pubkey here?
/// SCID is the short channel ID mapping to the federation
async fn routehints(
&self,
_num_route_hints: usize,
) -> Result<GetRouteHintsResponse, LightningRpcError> {
todo!()
}

/// Pay an invoice using the alby api
/// Pay needs to be idempotent, this is why we need lookup payment,
/// would need to do something similar with Alby
async fn pay(
&self,
request: PayInvoiceRequest,
) -> Result<PayInvoiceResponse, LightningRpcError> {
let client = reqwest::Client::new();
let endpoint = "https://api.getalby.com/payments/bolt11";

let req = json!({
"invoice": request.invoice,
});

let response = client
.post(endpoint)
.header("Authorization", format!("Bearer {}", self.api_key))
.json(&req)
.send()
.await
.unwrap();

let response = response.json::<AlbyPayResponse>().await.unwrap();

Ok(PayInvoiceResponse {
preimage: response.payment_preimage,
})
}

// FIXME: deduplicate implementation with pay
async fn pay_private(
&self,
_invoice: PrunedInvoice,
_max_delay: u64,
_max_fee: Amount,
) -> Result<PayInvoiceResponse, LightningRpcError> {
todo!()

// Ok(PayInvoiceResponse { preimage })
}

/// Returns true if the lightning backend supports payments without full
/// invoices
fn supports_private_payments(&self) -> bool {
false
}

async fn route_htlcs<'a>(
self: Box<Self>,
task_group: &mut TaskGroup,
) -> Result<(RouteHtlcStream<'a>, Arc<dyn ILnRpcClient>), LightningRpcError> {
const CHANNEL_SIZE: usize = 100;
let (gateway_sender, gateway_receiver) =
mpsc::channel::<Result<InterceptHtlcRequest, tonic::Status>>(CHANNEL_SIZE);

let new_client =
Arc::new(Self::new(self.bind_addr, self.api_key.clone(), self.outcomes.clone()).await);

run_webhook_server(
self.bind_addr,
task_group,
gateway_sender.clone(),
WebhookClient::Alby(*self),
)
.await
.map_err(|_| LightningRpcError::FailedToRouteHtlcs {
failure_reason: "Failed to start webhook server".to_string(),
})?;

Ok((Box::pin(ReceiverStream::new(gateway_receiver)), new_client))
}

async fn complete_htlc(
&self,
htlc: InterceptHtlcResponse,
) -> Result<EmptyResponse, LightningRpcError> {
send_htlc_to_webhook(&self.outcomes, htlc).await?;
Ok(EmptyResponse {})
}
}
Loading