Files
nym/nym-node/src/error.rs
T
Drazen Urch 90e9e3cff8 Domain fronting integration (#5974)
* feat: unify HTTP client creation and enable domain fronting

Enhanced the base nym_http_api_client to reduce fragmentation and enable domain fronting:

- Added SerializationFormat enum for explicit JSON/bincode choice (no auto-detection)
- Added from_network() method to create clients from NymNetworkDetails with domain fronting
- Added with_bincode() builder method for explicit serialization configuration
- Set Accept header based on serialization preference
- Added deprecation paths for NymApiClient wrapper and nym_api::Client re-export
- Enabled domain fronting support via network defaults feature

This is part of a broader effort to consolidate HTTP client implementations across the codebase,
reducing ~500 lines of wrapper code and providing automatic domain fronting for censorship resistance.

* feat: migrate NymApiClient usage to unified HTTP client

- Wire up domain fronting configuration in NymNetworkDetails
- Implement NymApiClientExt trait for base nym_http_api_client::Client
- Migrate direct NymApiClient usage in multiple components:
  - nym-network-monitor
  - verloc measurements
  - connection tester
  - coconut/ecash client
  - validator rewarder
- Add Copy derive to ApiUrlConst to enable iteration
- Update error handling and Display implementations

This enables automatic domain fronting for all Nym API calls via the configured CDN front hosts.

* fix: resolve all compilation errors after NymApiClient migration

- Add missing nym-http-api-client dependencies to multiple crates
- Add NymApiClientExt trait imports where needed
- Fix type mismatches from NymApiClient to unified Client
- Add error conversions for NymAPIError in various error enums
- Implement missing trait methods (get_current_rewarded_set, get_all_basic_nodes_with_metadata, get_all_described_nodes)
- Fix type conversions for RewardedSetResponse in network monitor
- Update all API client instantiation to use new unified HTTP client

* feat: complete migration to unified HTTP client and fix all compilation errors

- Added missing NymApiClientExt trait methods (get_all_expanded_nodes, change_base_urls)
- Fixed all compilation errors across the workspace
- Updated nym-node to use unified client instead of deprecated NymApiClient
- Fixed type conversions for RewardedSetResponse → EpochRewardedSet
- Added nym-http-api-client dependency where needed
- Updated all examples and documentation to use new client API

* fix: provide all API URLs for automatic failover in endpoint rotation

Previously, when rotating API endpoints, only a single URL was provided to the
HTTP client, defeating the purpose of having multiple URLs for resilience.

Changes:
- NymApiTopologyProvider now provides all URLs in rotated order when switching endpoints
- NymApisClient similarly provides all URLs starting from the working endpoint
- Added clarifying comments for broadcast/exhaustive query methods where single URLs are intentionally used
- This enables the HTTP client's built-in failover mechanism while maintaining endpoint rotation behavior

The fix ensures that if the primary endpoint fails, the client can automatically
failover to alternative endpoints without manual intervention, improving overall
network resilience.

* Update common/client-core/src/client/base_client/mod.rs

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>

* Remove error generics, address PR comments

* Explicit warning on missing fronting configuration

* Assorted CI fixes

* Registry proc-macro

* Rename macro

* Syn workspace version

* Where do we need to put inventory

* Ergonomics and call sites, incept the builder

* fix: Address critical issues in client configuration registry implementation

- Fixed HeaderMapInit parsing bug that would cause compilation errors
- Added comprehensive documentation with usage examples and DSL reference
- Improved error handling with better error messages for invalid headers
- Added test coverage for both macro and registry functionality
- Added debug inspection capabilities for registered configurations
- Fixed module name conflicts in tests by using separate modules

All tests now passing:
- 7 macro tests validating DSL parsing and code generation
- 4 registry tests verifying configuration collection and application

* Use default value for the ports until api is deployed

* Feature/improved http error (#6025)

* use display impl for urls

* feat: attempt to add more details to reqwest errors

* temporarily restored GenericRequestFailure variant

* another restoration

* cleanup

* Some debug tooling, and default timeout fix

* Fix user-agent override

* Fix various wasm things

---------

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
Co-authored-by: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
2025-09-15 14:32:15 +02:00

308 lines
9.1 KiB
Rust

// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::node::http::error::NymNodeHttpError;
use crate::wireguard::error::WireguardError;
use nym_http_api_client::HttpClientError;
use nym_ip_packet_router::error::ClientCoreError;
use nym_validator_client::nyxd::error::NyxdError;
use nym_validator_client::ValidatorClientError;
use std::io;
use std::net::IpAddr;
use std::path::PathBuf;
use thiserror::Error;
#[derive(Debug, Error)]
#[allow(clippy::enum_variant_names)]
pub enum KeyIOFailure {
#[error("failed to load {keys} keys from {:?} (private key) and {:?} (public key): {err}", .paths.private_key_path, .paths.public_key_path)]
KeyPairLoadFailure {
keys: String,
paths: nym_pemstore::KeyPairPath,
#[source]
err: io::Error,
},
#[error("failed to load {key} key from '{}': {err}", path.display())]
KeyLoadFailure {
key: String,
path: PathBuf,
#[source]
err: io::Error,
},
#[error("failed to store {keys} keys to {:?} (private key) and {:?} (public key): {err}", .paths.private_key_path, .paths.public_key_path)]
KeyPairStoreFailure {
keys: String,
paths: nym_pemstore::KeyPairPath,
#[source]
err: io::Error,
},
#[error("failed to store {key} key to '{}': {err}", path.display())]
KeyStoreFailure {
key: String,
path: PathBuf,
#[source]
err: io::Error,
},
#[error("failed to move {key} key from '{}' to '{}': {err}", source.display(), destination.display())]
KeyMoveFailure {
key: String,
source: PathBuf,
destination: PathBuf,
#[source]
err: io::Error,
},
#[error("failed to copy {key} key from '{}' to '{}': {err}", source.display(), destination.display())]
KeyCopyFailure {
key: String,
source: PathBuf,
destination: PathBuf,
#[source]
err: io::Error,
},
#[error("failed to remove {key} key from '{}': {err}", path.display())]
KeyRemovalFailure {
key: String,
path: PathBuf,
#[source]
err: io::Error,
},
}
#[derive(Debug, Error)]
pub enum NymNodeError {
#[error("this binary version no longer supports migration from legacy mixnodes and gateways")]
UnsupportedMigration,
#[error("failed to initialise shutdown signals: {source}")]
ShutdownSignalFailure {
#[source]
source: io::Error,
},
#[error("received shutdown signal while attempting to complete the action")]
ShutdownReceived,
#[error("could not find an existing config file at '{}' and fresh node initialisation has been disabled", config_path.display())]
ForbiddenInitialisation { config_path: PathBuf },
#[error("could not derive path to data directory of this nym node")]
DataDirDerivationFailure,
#[error(transparent)]
HttpFailure(#[from] NymNodeHttpError),
#[error("failed to load config file using path '{}'. detailed message: {source}", path.display())]
ConfigLoadFailure {
path: PathBuf,
#[source]
source: io::Error,
},
#[error("failed to save config file for id {id} using path '{}'. detailed message: {source}", path.display())]
ConfigSaveFailure {
id: String,
path: PathBuf,
#[source]
source: io::Error,
},
#[error("failed to validate loaded config: {error}")]
ConfigValidationFailure { error: String },
#[error("the node description file is malformed: {source}")]
MalformedDescriptionFile {
#[source]
source: toml::de::Error,
},
#[error("failed to load description file using path '{}'. detailed message: {source}", path.display())]
DescriptionLoadFailure {
path: PathBuf,
#[source]
source: io::Error,
},
#[error("failed to save description file using path '{}'. detailed message: {source}", path.display())]
DescriptionSaveFailure {
path: PathBuf,
#[source]
source: io::Error,
},
#[error("failed to write bonding information to '{}': {source}", path.display())]
BondingInfoWriteFailure {
path: PathBuf,
#[source]
source: io::Error,
},
#[error("this node hasn't set any valid public addresses to announce. Please modify [host.public_ips] section of your config")]
NoPublicIps,
#[error("there are no available nym api endpoints")]
NoNymApiUrls,
#[error("failed to resolve nym-api query - no nodes returned a valid response")]
NymApisExhausted,
#[error("the current epoch appears to be stuck")]
StuckEpoch,
// this should never happen in normal usage, but it's better to throw it than to panic
// in case of bugs
#[error("sphinx keys have already been consumed to spawn the node tasks")]
ConsumedSphinxKeys,
#[error("failed to resolve chain query: {0}")]
NyxdFailure(#[from] NyxdError),
#[error("this node attempted to announce an invalid public address: {address}. Please modify [host.public_ips] section of your config. Alternatively, if you wanted to use it in the local setting, run the node with the '--local' flag.")]
InvalidPublicIp { address: IpAddr },
#[error(transparent)]
WireguardError {
#[from]
source: WireguardError,
},
#[error("wireguard data is no longer available - has it been reused?")]
WireguardDataUnavailable,
#[deprecated]
#[error(transparent)]
KeyRecoveryError {
#[from]
source: nym_crypto::asymmetric::x25519::KeyRecoveryError,
},
#[error(transparent)]
KeyFailure(#[from] KeyIOFailure),
#[error("could not initialise nym-node as '--{name}' has not been specified which is required for a first time setup. (config section: {section})")]
MissingInitArg { section: String, name: String },
#[error("there was an issue with wireguard IP network: {source}")]
IpNetworkError {
#[from]
source: ipnetwork::IpNetworkError,
},
#[error(
"failed to retrieve initial network topology - can't start the node without it: {source}"
)]
InitialTopologyQueryFailure { source: ValidatorClientError },
#[error("experienced critical failure with the replay detection bloomfilter: {message}")]
BloomfilterFailure { message: &'static str },
#[error("failed to save/load the bloomfilter: {source} using path: {}", path.display())]
BloomfilterIoFailure { source: io::Error, path: PathBuf },
#[error("failed to deserialise bloomfilter metadata")]
BloomfilterMetadataDeserialisationFailure,
#[error(transparent)]
GatewayFailure(Box<nym_gateway::GatewayError>),
#[error(transparent)]
GatewayTasksStartupFailure(Box<dyn std::error::Error + Send + Sync>),
#[error(transparent)]
EntryGatewayFailure(Box<EntryGatewayError>),
#[error(transparent)]
ServiceProvidersFailure(#[from] ServiceProvidersError),
// TODO: more granular errors
#[error(transparent)]
ExternalClientCore(#[from] ClientCoreError),
#[error("failed upgrade")]
FailedUpgrade,
}
impl From<EntryGatewayError> for NymNodeError {
fn from(error: EntryGatewayError) -> Self {
NymNodeError::EntryGatewayFailure(Box::new(error))
}
}
impl From<nym_gateway::GatewayError> for NymNodeError {
fn from(error: nym_gateway::GatewayError) -> Self {
NymNodeError::GatewayFailure(Box::new(error))
}
}
impl NymNodeError {
pub fn config_validation_failure<S: Into<String>>(error: S) -> Self {
NymNodeError::ConfigValidationFailure {
error: error.into(),
}
}
pub fn bloomfilter_failure(message: &'static str) -> Self {
NymNodeError::BloomfilterFailure { message }
}
}
#[derive(Debug, Error)]
pub enum EntryGatewayError {
#[error(transparent)]
KeyFailure(#[from] KeyIOFailure),
// TODO: more granular errors
#[error(transparent)]
ExternalClientCore(#[from] ClientCoreError),
#[error("failed to load entry gateway account mnemonic from {}: {source}", path.display())]
MnemonicLoadFailure {
path: PathBuf,
#[source]
source: io::Error,
},
#[error("failed to save entry gateway account mnemonic from {}: {source}", path.display())]
MnemonicSaveFailure {
path: PathBuf,
#[source]
source: io::Error,
},
#[error("the stored mnemonic is malformed: {source}")]
MalformedBip39Mnemonic {
#[from]
source: bip39::Error,
},
#[error("entry gateway failure: {0}")]
External(Box<nym_gateway::GatewayError>),
}
impl From<nym_gateway::GatewayError> for EntryGatewayError {
fn from(error: nym_gateway::GatewayError) -> Self {
EntryGatewayError::External(Box::new(error))
}
}
#[derive(Debug, Error)]
pub enum ServiceProvidersError {
#[error(transparent)]
KeyFailure(#[from] KeyIOFailure),
// TODO: more granular errors
#[error(transparent)]
ExternalClientCore(#[from] ClientCoreError),
}
impl From<HttpClientError> for NymNodeError {
fn from(value: HttpClientError) -> Self {
Self::HttpFailure(NymNodeHttpError::from(value))
}
}