Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move account controller checks into the state machine connecting state #2020

Merged
merged 9 commits into from
Feb 12, 2025
Merged
Show file tree
Hide file tree
Changes from 5 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
3 changes: 3 additions & 0 deletions nym-vpn-core/Cargo.lock

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

1 change: 1 addition & 0 deletions nym-vpn-core/crates/nym-vpn-account-controller/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ nym-http-api-client.workspace = true
nym-sdk.workspace = true
nym-validator-client.workspace = true
nym-vpn-api-client = { path = "../nym-vpn-api-client" }
nym-vpn-lib-types = { path = "../nym-vpn-lib-types" }
nym-vpn-network-config = { path = "../nym-vpn-network-config" }
nym-vpn-store = { path = "../nym-vpn-store" }
nym-wg-gateway-client = { path = "../nym-wg-gateway-client" }
Expand Down
10 changes: 7 additions & 3 deletions nym-vpn-core/crates/nym-vpn-account-controller/src/commander.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@
// SPDX-License-Identifier: GPL-3.0-only

use nym_vpn_api_client::response::{NymVpnAccountSummaryResponse, NymVpnDevice, NymVpnUsage};
use nym_vpn_lib_types::AccountCommandError;
use nym_vpn_store::mnemonic::Mnemonic;
use tokio::sync::mpsc::UnboundedSender;

use crate::{
commands::{
request_zknym::RequestZkNymSummary, AccountCommand, AccountCommandError, ReturnSender,
},
commands::{request_zknym::RequestZkNymSummary, AccountCommand, ReturnSender},
error::Error,
shared_state::{AccountRegistered, DeviceState, SharedAccountState},
AvailableTicketbooks,
Expand Down Expand Up @@ -136,6 +135,7 @@ impl AccountControllerCommander {
pub async fn ensure_update_account(
&self,
) -> Result<Option<NymVpnAccountSummaryResponse>, AccountCommandError> {
tracing::debug!("Ensuring account is synced");
let state = self.shared_state.lock().await.clone();
match state.account_registered {
Some(AccountRegistered::Registered) => return Ok(None),
Expand All @@ -145,6 +145,7 @@ impl AccountControllerCommander {
}

pub async fn ensure_update_device(&self) -> Result<DeviceState, AccountCommandError> {
tracing::debug!("Ensuring device is synced");
let state = self.shared_state.lock().await.clone();
match state.device {
Some(DeviceState::Active) => return Ok(DeviceState::Active),
Expand All @@ -157,6 +158,7 @@ impl AccountControllerCommander {
}

pub async fn ensure_register_device(&self) -> Result<(), AccountCommandError> {
tracing::debug!("Ensuring device is registered");
let state = self.shared_state.lock().await.clone();
match state.device {
Some(DeviceState::Active) => return Ok(()),
Expand All @@ -169,6 +171,7 @@ impl AccountControllerCommander {
}

pub async fn ensure_available_zk_nyms(&self) -> Result<(), AccountCommandError> {
tracing::debug!("Ensuring available zk-nyms in the local credential store");
if self
.get_available_tickets()
.await?
Expand All @@ -194,6 +197,7 @@ impl AccountControllerCommander {
&self,
credential_mode: bool,
) -> Result<(), AccountCommandError> {
tracing::debug!("Waiting for account to be ready to connect");
self.ensure_update_account().await?;
self.ensure_update_device().await?;
self.ensure_register_device().await?;
Expand Down
127 changes: 1 addition & 126 deletions nym-vpn-core/crates/nym-vpn-account-controller/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@ pub(crate) mod request_zknym;
pub(crate) mod sync_account;
pub(crate) mod sync_device;

use nym_vpn_lib_types::AccountCommandError;
use nym_vpn_store::mnemonic::Mnemonic;
pub use register_device::RegisterDeviceError;
use request_zknym::RequestZkNymSummary;
pub use request_zknym::{RequestZkNymError, RequestZkNymSuccess};

use std::{collections::HashMap, sync::Arc};

use nym_vpn_api_client::response::{NymVpnAccountSummaryResponse, NymVpnDevice, NymVpnUsage};
use serde::{Deserialize, Serialize};
use tokio::sync::oneshot;

use crate::{shared_state::DeviceState, AvailableTicketbooks};
Expand Down Expand Up @@ -52,129 +50,6 @@ impl RunningCommands {
}
}

#[derive(Clone, Debug, thiserror::Error, PartialEq, Eq)]
pub enum AccountCommandError {
#[error("failed to sync account state: {0}")]
SyncAccountEndpointFailure(VpnApiEndpointFailure),

#[error("failed to get account state: {0}")]
GetAccountEndpointFailure(VpnApiEndpointFailure),

#[error("failed to sync device state: {0}")]
SyncDeviceEndpointFailure(VpnApiEndpointFailure),

#[error("failed to register device: {0}")]
RegisterDeviceEndpointFailure(VpnApiEndpointFailure),

#[error("failed to request zk nym")]
RequestZkNym {
successes: Vec<RequestZkNymSuccess>,
failed: Vec<RequestZkNymError>,
},

#[error("failed to request zk nym")]
RequestZkNymGeneral(RequestZkNymError),

#[error("no account stored")]
NoAccountStored,

#[error("no device stored")]
NoDeviceStored,

#[error("device registration is in progress")]
RegistrationInProgress,

#[error("failed to remove account: {0}")]
RemoveAccount(String),

#[error("failed to remove device from nym vpn api: {0}")]
UnregisterDeviceApiClientFailure(String),

#[error("failed to remove device identity: {0}")]
RemoveDeviceIdentity(String),

#[error("failed to reset credential storage: {0}")]
ResetCredentialStorage(String),

#[error("failed to remove account files: {0}")]
RemoveAccountFiles(String),

#[error("failed to init device keys: {0}")]
InitDeviceKeys(String),

// Catch all for any other error
#[error("general error: {0}")]
General(String),

// Internal error that should not happen
#[error("internal error: {0}")]
Internal(String),
}

impl From<RegisterDeviceError> for AccountCommandError {
fn from(err: RegisterDeviceError) -> Self {
match err {
RegisterDeviceError::RegisterDeviceEndpointFailure(failure) => {
AccountCommandError::RegisterDeviceEndpointFailure(failure)
}
RegisterDeviceError::General(message) => AccountCommandError::General(message),
}
}
}

impl From<RequestZkNymError> for AccountCommandError {
fn from(err: RequestZkNymError) -> Self {
AccountCommandError::RequestZkNymGeneral(err)
}
}

impl From<RequestZkNymSummary> for AccountCommandError {
fn from(summary: RequestZkNymSummary) -> Self {
let (successes, failed): (Vec<_>, Vec<_>) = summary.into_iter().partition(Result::is_ok);
let successes = successes.into_iter().map(Result::unwrap).collect();
let failed = failed.into_iter().map(Result::unwrap_err).collect();
Self::RequestZkNym { successes, failed }
}
}

impl AccountCommandError {
pub fn internal(message: impl ToString) -> Self {
AccountCommandError::Internal(message.to_string())
}

pub fn general(message: impl ToString) -> Self {
AccountCommandError::General(message.to_string())
}
}

#[derive(thiserror::Error, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[error("nym-vpn-api error: message={message}, message_id={message_id:?}, code_reference_id={code_reference_id:?}")]
pub struct VpnApiEndpointFailure {
pub message: String,
pub message_id: Option<String>,
pub code_reference_id: Option<String>,
}

impl From<nym_vpn_api_client::response::NymErrorResponse> for VpnApiEndpointFailure {
fn from(response: nym_vpn_api_client::response::NymErrorResponse) -> Self {
Self {
message: response.message,
message_id: response.message_id,
code_reference_id: response.code_reference_id,
}
}
}

impl TryFrom<nym_vpn_api_client::VpnApiClientError> for VpnApiEndpointFailure {
type Error = nym_vpn_api_client::VpnApiClientError;

fn try_from(response: nym_vpn_api_client::VpnApiClientError) -> Result<Self, Self::Error> {
nym_vpn_api_client::response::extract_error_response(&response)
.map(VpnApiEndpointFailure::from)
.ok_or(response)
}
}

#[derive(Debug)]
pub struct ReturnSender<T> {
sender: oneshot::Sender<Result<T, AccountCommandError>>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ use nym_vpn_api_client::{
types::{Device, VpnApiAccount},
VpnApiClient,
};
use serde::{Deserialize, Serialize};
use nym_vpn_lib_types::{RegisterDeviceError, VpnApiErrorResponse};

use crate::{
shared_state::{DeviceState, RegisterDeviceResult},
SharedAccountState,
};

use super::{AccountCommandError, AccountCommandResult, VpnApiEndpointFailure};
use super::{AccountCommandError, AccountCommandResult};

pub(crate) struct RegisterDeviceCommandHandler {
id: uuid::Uuid,
Expand Down Expand Up @@ -86,43 +86,12 @@ impl RegisterDeviceCommandHandler {
self.account_state
.set_device_registration(RegisterDeviceResult::Failed(err.clone()))
.await;
Err(AccountCommandError::from(err))
Err(AccountCommandError::RegisterDevice(err))
}
}
}
}

#[derive(Debug, thiserror::Error, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum RegisterDeviceError {
#[error("failed to register device: {0}")]
RegisterDeviceEndpointFailure(VpnApiEndpointFailure),

#[error("failed to register device: {0}")]
General(String),
}

impl RegisterDeviceError {
pub(crate) fn general(message: impl ToString) -> Self {
RegisterDeviceError::General(message.to_string())
}

pub fn message(&self) -> String {
match self {
RegisterDeviceError::RegisterDeviceEndpointFailure(failure) => failure.message.clone(),
RegisterDeviceError::General(message) => message.clone(),
}
}

pub fn message_id(&self) -> Option<String> {
match self {
RegisterDeviceError::RegisterDeviceEndpointFailure(failure) => {
failure.message_id.clone()
}
RegisterDeviceError::General(_) => None,
}
}
}

pub(crate) async fn register_device(
account: &VpnApiAccount,
device: &Device,
Expand All @@ -133,16 +102,9 @@ pub(crate) async fn register_device(
.register_device(account, device)
.await
.map_err(|err| {
nym_vpn_api_client::response::extract_error_response(&err)
.map(|e| {
tracing::warn!(message = %e.message, message_id=?e.message_id, code_reference_id=?e.code_reference_id, "nym-vpn-api reports");
RegisterDeviceError::RegisterDeviceEndpointFailure(VpnApiEndpointFailure {
message_id: e.message_id.clone(),
message: e.message.clone(),
code_reference_id: e.code_reference_id.clone(),
})
})
.unwrap_or_else(|| RegisterDeviceError::general(err))
VpnApiErrorResponse::try_from(err)
.map(RegisterDeviceError::RegisterDeviceEndpointFailure)
.unwrap_or_else(RegisterDeviceError::unexpected_response)
})?;

tracing::info!("Response: {:#?}", response);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@ use std::{collections::HashMap, sync::Arc};

use nym_credential_proxy_requests::api::v1::ticketbook::models::PartialVerificationKeysResponse;
use nym_vpn_api_client::VpnApiClient;

use crate::VpnApiEndpointFailure;

use super::RequestZkNymError;
use nym_vpn_lib_types::{RequestZkNymError, VpnApiErrorResponse};

// Generic struct to store cached data during the request process, both between concurrent requests
// for different types, and between requests for the same type.
Expand Down Expand Up @@ -47,10 +44,10 @@ impl CachedData {
.get_directory_zk_nyms_ticketbook_partial_verification_keys()
.await
.map_err(|err| {
VpnApiEndpointFailure::try_from(err)
VpnApiErrorResponse::try_from(err)
.map(|source| {
RequestZkNymError::GetPartialVerificationKeysEndpointFailure {
source,
response: source,
epoch_id,
}
})
Expand Down
Loading
Loading