Skip to content

Commit

Permalink
Merge pull request #1018 from MutinyWallet/voltage-async
Browse files Browse the repository at this point in the history
Save Voltage pubkey and connection string to storage
  • Loading branch information
benthecarman authored Feb 12, 2024
2 parents 2874b2d + be0954c commit 1957174
Show file tree
Hide file tree
Showing 6 changed files with 279 additions and 114 deletions.
8 changes: 4 additions & 4 deletions mutiny-core/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -408,10 +408,10 @@ impl<S: MutinyStorage> EventHandler<S> {
Err(e) => log_debug!(self.logger, "EVENT: OpenChannelRequest error: {e:?}"),
};

let lsp_pubkey = self
.lsp_client
.as_ref()
.map(|client| client.get_lsp_pubkey());
let lsp_pubkey = match self.lsp_client {
Some(ref lsp) => Some(lsp.get_lsp_pubkey().await),
None => None,
};

if lsp_pubkey.as_ref() != Some(&counterparty_node_id) {
// did not match the lsp pubkey, normal open
Expand Down
10 changes: 5 additions & 5 deletions mutiny-core/src/lsp/lsps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ impl<S: MutinyStorage> Lsp for LspsClient<S> {

let inbound_capacity_msat: u64 = self
.channel_manager
.list_channels_with_counterparty(&self.get_lsp_pubkey())
.list_channels_with_counterparty(&self.pubkey)
.iter()
.map(|c| c.inbound_capacity_msat)
.sum();
Expand Down Expand Up @@ -486,17 +486,17 @@ impl<S: MutinyStorage> Lsp for LspsClient<S> {
Ok(invoice)
}

fn get_lsp_pubkey(&self) -> PublicKey {
async fn get_lsp_pubkey(&self) -> PublicKey {
self.pubkey
}

fn get_lsp_connection_string(&self) -> String {
async fn get_lsp_connection_string(&self) -> String {
self.connection_string.clone()
}

fn get_config(&self) -> LspConfig {
async fn get_config(&self) -> LspConfig {
LspConfig::Lsps(LspsConfig {
connection_string: self.get_lsp_connection_string(),
connection_string: self.connection_string.clone(),
token: self.token.clone(),
})
}
Expand Down
81 changes: 58 additions & 23 deletions mutiny-core/src/lsp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ use crate::error::MutinyError;
use crate::keymanager::PhantomKeysManager;
use crate::ldkstorage::PhantomChannelManager;
use crate::logging::MutinyLogger;
use crate::lsp::voltage::VoltageConfig;
use crate::node::LiquidityManager;
use crate::storage::MutinyStorage;
use async_lock::RwLock;
use async_trait::async_trait;
use bitcoin::secp256k1::PublicKey;
use bitcoin::Network;
Expand All @@ -20,13 +22,17 @@ pub mod voltage;

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum LspConfig {
VoltageFlow(String),
VoltageFlow(VoltageConfig),
Lsps(LspsConfig),
}

impl LspConfig {
pub fn new_voltage_flow(url: String) -> Self {
Self::VoltageFlow(url)
Self::VoltageFlow(VoltageConfig {
url,
pubkey: None,
connection_string: None,
})
}

pub fn new_lsps(connection_string: String, token: Option<String>) -> Self {
Expand All @@ -42,6 +48,16 @@ impl LspConfig {
LspConfig::Lsps(_) => true,
}
}

/// Checks if the two LSP configs are functionally equivalent, even if they do not
/// contain the same data.
pub fn matches(&self, other: &Self) -> bool {
match (self, other) {
(LspConfig::VoltageFlow(conf), LspConfig::VoltageFlow(other)) => conf.url == other.url,
(LspConfig::Lsps(conf), LspConfig::Lsps(other)) => conf == other,
_ => false,
}
}
}

pub fn deserialize_lsp_config<'de, D>(deserializer: D) -> Result<Option<LspConfig>, D::Error>
Expand All @@ -50,7 +66,11 @@ where
{
let v: Option<Value> = Option::deserialize(deserializer)?;
match v {
Some(Value::String(s)) => Ok(Some(LspConfig::VoltageFlow(s))),
Some(Value::String(s)) => Ok(Some(LspConfig::VoltageFlow(VoltageConfig {
url: s,
pubkey: None,
connection_string: None,
}))),
Some(Value::Object(_)) => LspConfig::deserialize(v.unwrap())
.map(Some)
.map_err(|e| serde::de::Error::custom(format!("invalid lsp config: {e}"))),
Expand Down Expand Up @@ -95,21 +115,23 @@ pub(crate) trait Lsp {
&self,
invoice_request: InvoiceRequest,
) -> Result<Bolt11Invoice, MutinyError>;
fn get_lsp_pubkey(&self) -> PublicKey;
fn get_lsp_connection_string(&self) -> String;
async fn get_lsp_pubkey(&self) -> PublicKey;
async fn get_lsp_connection_string(&self) -> String;
fn get_expected_skimmed_fee_msat(&self, payment_hash: PaymentHash, payment_size: u64) -> u64;
fn get_config(&self) -> LspConfig;
async fn get_config(&self) -> LspConfig;
}

#[derive(Clone)]
pub enum AnyLsp<S: MutinyStorage> {
VoltageFlow(LspClient),
VoltageFlow(Arc<RwLock<LspClient>>),
Lsps(LspsClient<S>),
}

impl<S: MutinyStorage> AnyLsp<S> {
pub async fn new_voltage_flow(url: &str) -> Result<Self, MutinyError> {
Ok(Self::VoltageFlow(LspClient::new(url).await?))
pub async fn new_voltage_flow(config: VoltageConfig) -> Result<Self, MutinyError> {
Ok(Self::VoltageFlow(Arc::new(RwLock::new(
LspClient::new(config).await?,
))))
}

#[allow(clippy::too_many_arguments)]
Expand Down Expand Up @@ -149,7 +171,10 @@ impl<S: MutinyStorage> AnyLsp<S> {
impl<S: MutinyStorage> Lsp for AnyLsp<S> {
async fn get_lsp_fee_msat(&self, fee_request: FeeRequest) -> Result<FeeResponse, MutinyError> {
match self {
AnyLsp::VoltageFlow(client) => client.get_lsp_fee_msat(fee_request).await,
AnyLsp::VoltageFlow(lock) => {
let client = lock.read().await;
client.get_lsp_fee_msat(fee_request).await
}
AnyLsp::Lsps(client) => client.get_lsp_fee_msat(fee_request).await,
}
}
Expand All @@ -159,37 +184,47 @@ impl<S: MutinyStorage> Lsp for AnyLsp<S> {
invoice_request: InvoiceRequest,
) -> Result<Bolt11Invoice, MutinyError> {
match self {
AnyLsp::VoltageFlow(client) => client.get_lsp_invoice(invoice_request).await,
AnyLsp::VoltageFlow(lock) => {
let client = lock.read().await;
client.get_lsp_invoice(invoice_request).await
}
AnyLsp::Lsps(client) => client.get_lsp_invoice(invoice_request).await,
}
}

fn get_lsp_pubkey(&self) -> PublicKey {
async fn get_lsp_pubkey(&self) -> PublicKey {
match self {
AnyLsp::VoltageFlow(client) => client.get_lsp_pubkey(),
AnyLsp::Lsps(client) => client.get_lsp_pubkey(),
AnyLsp::VoltageFlow(lock) => {
let client = lock.read().await;
client.get_lsp_pubkey().await
}
AnyLsp::Lsps(client) => client.get_lsp_pubkey().await,
}
}

fn get_lsp_connection_string(&self) -> String {
async fn get_lsp_connection_string(&self) -> String {
match self {
AnyLsp::VoltageFlow(client) => client.get_lsp_connection_string(),
AnyLsp::Lsps(client) => client.get_lsp_connection_string(),
AnyLsp::VoltageFlow(lock) => {
let client = lock.read().await;
client.get_lsp_connection_string().await
}
AnyLsp::Lsps(client) => client.get_lsp_connection_string().await,
}
}

fn get_config(&self) -> LspConfig {
async fn get_config(&self) -> LspConfig {
match self {
AnyLsp::VoltageFlow(client) => client.get_config(),
AnyLsp::Lsps(client) => client.get_config(),
AnyLsp::VoltageFlow(lock) => {
let client = lock.read().await;
client.get_config().await
}
AnyLsp::Lsps(client) => client.get_config().await,
}
}

fn get_expected_skimmed_fee_msat(&self, payment_hash: PaymentHash, payment_size: u64) -> u64 {
match self {
AnyLsp::VoltageFlow(client) => {
client.get_expected_skimmed_fee_msat(payment_hash, payment_size)
}
AnyLsp::VoltageFlow(_) => 0,
AnyLsp::Lsps(client) => {
client.get_expected_skimmed_fee_msat(payment_hash, payment_size)
}
Expand Down
105 changes: 92 additions & 13 deletions mutiny-core/src/lsp/voltage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,61 @@ use bitcoin::secp256k1::PublicKey;
use lightning::ln::PaymentHash;
use lightning_invoice::Bolt11Invoice;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value;
use std::str::FromStr;

use super::FeeResponse;

#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct VoltageConfig {
pub url: String,
pub pubkey: Option<PublicKey>,
pub connection_string: Option<String>,
}

// Need custom Deserializer to handle old encoding
impl<'de> Deserialize<'de> for VoltageConfig {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value: Value = Value::deserialize(deserializer)?;
match value {
// old encoding was a string, parse as the url
Value::String(url) => Ok(VoltageConfig {
url,
pubkey: None,
connection_string: None,
}),
// new encoding is an object, parse as such
Value::Object(map) => {
let url = map
.get("url")
.and_then(Value::as_str)
.ok_or_else(|| serde::de::Error::missing_field("url"))?
.to_string();
let pubkey = map
.get("pubkey")
.and_then(Value::as_str)
.map(PublicKey::from_str)
.transpose()
.map_err(|_| serde::de::Error::custom("invalid pubkey"))?;
let connection_string = map
.get("connection_string")
.and_then(Value::as_str)
.map(String::from);
Ok(VoltageConfig {
url,
pubkey,
connection_string,
})
}
_ => Err(serde::de::Error::custom("invalid value for VoltageConfig")),
}
}
}

#[derive(Clone, Debug)]
pub(crate) struct LspClient {
pub pubkey: PublicKey,
Expand Down Expand Up @@ -70,13 +120,33 @@ const PROPOSAL_PATH: &str = "/api/v1/proposal";
const FEE_PATH: &str = "/api/v1/fee";

impl LspClient {
pub async fn new(url: &str) -> Result<Self, MutinyError> {
pub async fn new(config: VoltageConfig) -> Result<Self, MutinyError> {
let http_client = Client::new();

// if we have both pubkey and connection string, use them, otherwise request them from the LSP
let (pubkey, connection_string) = match (config.pubkey, config.connection_string) {
(Some(pk), Some(string)) => (pk, string),
_ => Self::fetch_connection_info(&http_client, &config.url).await?,
};

Ok(LspClient {
pubkey,
url: config.url,
connection_string,
http_client,
})
}

/// Get the pubkey and connection string from the LSP from the /info endpoint
async fn fetch_connection_info(
http_client: &Client,
url: &str,
) -> Result<(PublicKey, String), MutinyError> {
let request = http_client
.get(format!("{}{}", url, GET_INFO_PATH))
.build()
.map_err(|_| MutinyError::LspGenericError)?;
let response: reqwest::Response = utils::fetch_with_timeout(&http_client, request).await?;
let response: reqwest::Response = utils::fetch_with_timeout(http_client, request).await?;

let get_info_response: GetInfoResponse = response
.json()
Expand Down Expand Up @@ -108,12 +178,17 @@ impl LspClient {
})
.ok_or_else(|| anyhow::anyhow!("No suitable connection method found"))?;

Ok(LspClient {
pubkey: get_info_response.pubkey,
url: String::from(url),
connection_string,
http_client,
})
Ok((get_info_response.pubkey, connection_string))
}

/// Get the pubkey and connection string from the LSP from the /info endpoint
/// and set them on the LSP client
pub(crate) async fn set_connection_info(&mut self) -> Result<(), MutinyError> {
let (pubkey, connection_string) =
Self::fetch_connection_info(&self.http_client, &self.url).await?;
self.pubkey = pubkey;
self.connection_string = connection_string;
Ok(())
}
}

Expand Down Expand Up @@ -196,16 +271,20 @@ impl Lsp for LspClient {
Ok(fee_response)
}

fn get_lsp_pubkey(&self) -> PublicKey {
async fn get_lsp_pubkey(&self) -> PublicKey {
self.pubkey
}

fn get_lsp_connection_string(&self) -> String {
async fn get_lsp_connection_string(&self) -> String {
self.connection_string.clone()
}

fn get_config(&self) -> LspConfig {
LspConfig::VoltageFlow(self.url.clone())
async fn get_config(&self) -> LspConfig {
LspConfig::VoltageFlow(VoltageConfig {
url: self.url.clone(),
pubkey: Some(self.pubkey),
connection_string: Some(self.connection_string.clone()),
})
}

fn get_expected_skimmed_fee_msat(&self, _payment_hash: PaymentHash, _payment_size: u64) -> u64 {
Expand Down
Loading

0 comments on commit 1957174

Please sign in to comment.