Skip to content

Commit

Permalink
Merge pull request fedimint#6051 from m1sterc001guy/error_refactor
Browse files Browse the repository at this point in the history
refactor: gateway error refactor
  • Loading branch information
m1sterc001guy authored Sep 23, 2024
2 parents caf3aa3 + 7601d63 commit a53de07
Show file tree
Hide file tree
Showing 6 changed files with 476 additions and 330 deletions.
48 changes: 28 additions & 20 deletions gateway/ln-gateway/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ use fedimint_core::db::{Database, IDatabaseTransactionOpsCoreTyped};
use fedimint_core::module::registry::ModuleDecoderRegistry;

use crate::db::FederationConfig;
use crate::error::AdminGatewayError;
use crate::gateway_module_v2::GatewayClientInitV2;
use crate::state_machine::GatewayClientInit;
use crate::{Gateway, GatewayError, Result};
use crate::{AdminResult, Gateway};

#[derive(Debug, Clone)]
pub struct GatewayClientBuilder {
Expand All @@ -42,8 +43,10 @@ impl GatewayClientBuilder {

/// Reads a plain root secret from a database to construct a database.
/// Only used for "legacy" federations before v0.5.0
async fn client_plainrootsecret(&self, db: &Database) -> Result<DerivableSecret> {
let client_secret = Client::load_decodable_client_secret::<[u8; 64]>(db).await?;
async fn client_plainrootsecret(&self, db: &Database) -> AdminResult<DerivableSecret> {
let client_secret = Client::load_decodable_client_secret::<[u8; 64]>(db)
.await
.map_err(AdminGatewayError::ClientCreationError)?;
Ok(PlainRootSecretStrategy::to_root_secret(&client_secret))
}

Expand All @@ -54,7 +57,7 @@ impl GatewayClientBuilder {
db: Database,
federation_config: &FederationConfig,
gateway: Arc<Gateway>,
) -> Result<ClientBuilder> {
) -> AdminResult<ClientBuilder> {
let FederationConfig {
federation_index,
timelock_delta,
Expand All @@ -75,7 +78,7 @@ impl GatewayClientBuilder {

let mut client_builder = Client::builder(db)
.await
.map_err(GatewayError::DatabaseError)?;
.map_err(AdminGatewayError::ClientCreationError)?;
client_builder.with_module_inits(registry);
client_builder.with_primary_module(self.primary_module);
client_builder.with_connector(connector);
Expand All @@ -91,11 +94,12 @@ impl GatewayClientBuilder {
config: FederationConfig,
gateway: Arc<Gateway>,
mnemonic: &Mnemonic,
) -> Result<()> {
) -> AdminResult<()> {
let client_config = config
.connector
.download_from_invite_code(&config.invite_code)
.await?;
.await
.map_err(AdminGatewayError::ClientCreationError)?;
let federation_id = config.invite_code.federation_id();
let db = gateway
.gateway_db
Expand All @@ -110,7 +114,8 @@ impl GatewayClientBuilder {
&client_config,
config.invite_code.api_secret(),
)
.await?;
.await
.map_err(AdminGatewayError::ClientCreationError)?;
let client = client_builder
.recover(
secret.clone(),
Expand All @@ -120,8 +125,11 @@ impl GatewayClientBuilder {
)
.await
.map(Arc::new)
.map_err(GatewayError::ClientStateMachineError)?;
client.wait_for_all_recoveries().await?;
.map_err(AdminGatewayError::ClientCreationError)?;
client
.wait_for_all_recoveries()
.await
.map_err(AdminGatewayError::ClientCreationError)?;
Ok(())
}

Expand All @@ -132,15 +140,14 @@ impl GatewayClientBuilder {
config: FederationConfig,
gateway: Arc<Gateway>,
mnemonic: &Mnemonic,
) -> Result<fedimint_client::ClientHandleArc> {
) -> AdminResult<fedimint_client::ClientHandleArc> {
let invite_code = config.invite_code.clone();
let federation_id = invite_code.federation_id();
let db_path = self.work_dir.join(format!("{federation_id}.db"));

let (db, root_secret) = if db_path.exists() {
let rocksdb = fedimint_rocksdb::RocksDb::open(db_path.clone()).map_err(|e| {
GatewayError::DatabaseError(anyhow::anyhow!("Error opening rocksdb: {e:?}"))
})?;
let rocksdb = fedimint_rocksdb::RocksDb::open(db_path.clone())
.map_err(AdminGatewayError::ClientCreationError)?;
let db = Database::new(rocksdb, ModuleDecoderRegistry::default());
let root_secret = self.client_plainrootsecret(&db).await?;
(db, root_secret)
Expand All @@ -162,24 +169,25 @@ impl GatewayClientBuilder {
let client_config = config
.connector
.download_from_invite_code(&invite_code)
.await?;
.await
.map_err(AdminGatewayError::ClientCreationError)?;
client_builder
.join(root_secret, client_config.clone(), invite_code.api_secret())
.await
}
.map(Arc::new)
.map_err(GatewayError::ClientStateMachineError)
.map_err(AdminGatewayError::ClientCreationError)
}

/// Verifies that the saved `ClientConfig` contains the expected
/// federation's config.
async fn verify_client_config(db: &Database, federation_id: FederationId) -> Result<()> {
async fn verify_client_config(db: &Database, federation_id: FederationId) -> AdminResult<()> {
let mut dbtx = db.begin_transaction_nc().await;
if let Some(config) = dbtx.get_value(&ClientConfigKey).await {
if config.calculate_federation_id() != federation_id {
return Err(GatewayError::ClientCreationError(
"Federation Id did not match saved federation ID".to_string(),
));
return Err(AdminGatewayError::ClientCreationError(anyhow::anyhow!(
"Federation Id did not match saved federation ID".to_string()
)));
}
}
Ok(())
Expand Down
144 changes: 144 additions & 0 deletions gateway/ln-gateway/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
use std::fmt::Display;

use axum::response::{IntoResponse, Response};
use fedimint_core::config::{FederationId, FederationIdPrefix};
use fedimint_core::fmt_utils::OptStacktrace;
use reqwest::StatusCode;
use thiserror::Error;
use tracing::error;

use crate::lightning::LightningRpcError;
use crate::state_machine::pay::OutgoingPaymentError;

/// Errors that unauthenticated endpoints can encounter. For privacy reasons,
/// the error messages are intended to be redacted before returning to the
/// client.
#[derive(Debug, Error)]
pub enum PublicGatewayError {
#[error("Lightning rpc error: {}", .0)]
Lightning(#[from] LightningRpcError),
#[error("LNv1 error: {:?}", .0)]
LNv1(#[from] LNv1Error),
#[error("LNv2 error: {:?}", .0)]
LNv2(#[from] LNv2Error),
#[error("{}", .0)]
FederationNotConnected(#[from] FederationNotConnected),
#[error("Failed to receive ecash: {failure_reason}")]
ReceiveEcashError { failure_reason: String },
}

impl IntoResponse for PublicGatewayError {
fn into_response(self) -> Response {
// For privacy reasons, we do not return too many details about the failure of
// the request back to the client to prevent malicious clients from
// deducing state about the gateway/lightning node.
error!("{self}");
let (error_message, status_code) = match self {
PublicGatewayError::FederationNotConnected(e) => {
(e.to_string(), StatusCode::BAD_REQUEST)
}
PublicGatewayError::ReceiveEcashError { .. } => (
"Failed to receive ecash".to_string(),
StatusCode::INTERNAL_SERVER_ERROR,
),
PublicGatewayError::Lightning(_) => (
"Lightning Network operation failed".to_string(),
StatusCode::INTERNAL_SERVER_ERROR,
),
PublicGatewayError::LNv1(_) => (
"LNv1 operation failed, please contact gateway operator".to_string(),
StatusCode::INTERNAL_SERVER_ERROR,
),
PublicGatewayError::LNv2(_) => (
"LNv2 operation failed, please contact gateway operator".to_string(),
StatusCode::INTERNAL_SERVER_ERROR,
),
};

Response::builder()
.status(status_code)
.body(error_message.into())
.expect("Failed to create Response")
}
}

/// Errors that authenticated endpoints can encounter. Full error message and
/// error details are returned to the admin client for debugging purposes.
#[derive(Debug, Error)]
pub enum AdminGatewayError {
#[error("Failed to create a federation client: {}", OptStacktrace(.0))]
ClientCreationError(anyhow::Error),
#[error("Failed to remove a federation client: {}", OptStacktrace(.0))]
ClientRemovalError(String),
#[error("There was an error with the Gateway's mnemonic: {}", OptStacktrace(.0))]
MnemonicError(anyhow::Error),
#[error("Unexpected Error: {}", OptStacktrace(.0))]
Unexpected(#[from] anyhow::Error),
#[error("{}", .0)]
FederationNotConnected(#[from] FederationNotConnected),
#[error("Error configuring the gateway: {}", OptStacktrace(.0))]
GatewayConfigurationError(String),
#[error("Lightning error: {}", OptStacktrace(.0))]
Lightning(#[from] LightningRpcError),
#[error("Error registering federation {federation_id}")]
RegistrationError { federation_id: FederationId },
#[error("Error withdrawing funds onchain: {failure_reason}")]
WithdrawError { failure_reason: String },
}

impl IntoResponse for AdminGatewayError {
// For admin errors, always pass along the full error message for debugging
// purposes
fn into_response(self) -> Response {
error!("{self}");
Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(self.to_string().into())
.expect("Failed to create Response")
}
}

/// Errors that can occur during the LNv1 protocol. LNv1 errors are public and
/// the error messages should be redacted for privacy reasons.
#[derive(Debug, Error)]
pub enum LNv1Error {
#[error("Incoming payment error: {}", OptStacktrace(.0))]
IncomingPayment(String),
#[error(
"Outgoing Contract Error Reason: {message} Stack: {}",
OptStacktrace(error)
)]
OutgoingContract {
error: Box<OutgoingPaymentError>,
message: String,
},
#[error("Outgoing Payment Error: {}", OptStacktrace(.0))]
OutgoingPayment(#[from] anyhow::Error),
}

/// Errors that can occur during the LNv2 protocol. LNv2 errors are public and
/// the error messages should be redacted for privacy reasons.
#[derive(Debug, Error)]
pub enum LNv2Error {
#[error("Incoming Payment Error: {}", .0)]
IncomingPayment(String),
#[error("Outgoing Payment Error: {}", OptStacktrace(.0))]
OutgoingPayment(#[from] anyhow::Error),
}

/// Public error that indicates the requested federation is not connected to
/// this gateway.
#[derive(Debug, Error)]
pub struct FederationNotConnected {
pub federation_id_prefix: FederationIdPrefix,
}

impl Display for FederationNotConnected {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"No federation available for prefix {}",
self.federation_id_prefix
)
}
}
Loading

0 comments on commit a53de07

Please sign in to comment.