Skip to content

Commit

Permalink
[refactor]: rehearse errors
Browse files Browse the repository at this point in the history
Signed-off-by: Dmitry Balashov <[email protected]>
  • Loading branch information
0x009922 committed Apr 17, 2024
1 parent a8e315a commit 786b8c6
Show file tree
Hide file tree
Showing 9 changed files with 158 additions and 99 deletions.
86 changes: 42 additions & 44 deletions cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ impl Iroha<ToriiNotStarted> {
let (events_sender, _) = broadcast::channel(10000);
let world = World::with(
[genesis_domain(config.genesis.public_key().clone())],
config.sumeragi.trusted_peers.clone(),
config.sumeragi.trusted_peers.value().clone(),
);

let kura = Kura::new(&config.kura).change_context(StartError::InitKura)?;
Expand Down Expand Up @@ -577,9 +577,28 @@ fn genesis_domain(public_key: PublicKey) -> Domain {
}

/// Error of [`read_config_and_genesis`]
#[derive(Error, Debug, Copy, Clone)]
#[error("Configuration error")]
pub struct ReadConfigError;
#[derive(Error, Debug)]
#[allow(missing_docs)]
pub enum ConfigError {
#[error("Error occurred while reading configuration from file(s) and environment")]
ReadConfig,
#[error("Error occurred while reading genesis block")]
ReadGenesis,
#[error("The network consists from this one peer only")]
LonePeer,
#[cfg(feature = "dev-telemetry")]
#[error("Telemetry output file path is root or empty")]
TelemetryOutFileIsRootOrEmpty,
#[cfg(feature = "dev-telemetry")]
#[error("Telemetry output file path is a directory")]
TelemetryOutFileIsDir,
#[error("Torii and Network addresses are the same, but should be different")]
SameNetworkAndToriiAddrs,
#[error("Invalid directory path found")]
InvalidDirPath,
#[error("Cannot bind a listener to address `{addr}`")]
CannotBindAddress { addr: SocketAddr },
}

/// Read the configuration and then a genesis block if specified.
///
Expand All @@ -589,24 +608,22 @@ pub struct ReadConfigError;
/// - If failed to build a genesis network
pub fn read_config_and_genesis(
args: &Args,
) -> Result<(Config, LoggerInitConfig, Option<GenesisNetwork>), ReadConfigError> {
) -> Result<(Config, LoggerInitConfig, Option<GenesisNetwork>), ConfigError> {
use iroha_config::parameters::actual::Genesis;

let mut reader = ConfigReader::new();

if let Some(path) = &args.config {
reader = reader
.read_toml_with_extends(path)
.change_context(ReadConfigError)?;
.change_context(ConfigError::ReadConfig)?;
}

let config = reader
.read_and_complete::<UserConfig>()
.change_context(ReadConfigError)?
.change_context(ConfigError::ReadConfig)?
.parse()
.change_context(ReadConfigError)?;

validate_config(&config, args.submit_genesis).change_context(ReadConfigError)?;
.change_context(ConfigError::ReadConfig)?;

let genesis = if let Genesis::Full { key_pair, file } = &config.genesis {
let raw_block = RawGenesisBlock::from_path(file.resolve_relative_path())
Expand All @@ -615,7 +632,7 @@ pub fn read_config_and_genesis(
.map_err(|report|
report
.attach_printable(file.clone().into_attachment().display_path())
.change_context(ReadConfigError)
.change_context(ConfigError::ReadGenesis)
)?;

Some(GenesisNetwork::new(
Expand All @@ -627,30 +644,14 @@ pub fn read_config_and_genesis(
None
};

validate_config(&config, args.submit_genesis)?;

let logger_config = LoggerInitConfig::new(config.logger, args.terminal_colors);

Ok((config, logger_config, genesis))
}

#[derive(Error, Debug)]
enum ConfigValidateError {
#[error("The network consists from this one peer only")]
LonePeer,
#[cfg(feature = "dev-telemetry")]
#[error("Telemetry output file path is root or empty")]
TelemetryOutFileIsRootOrEmpty,
#[cfg(feature = "dev-telemetry")]
#[error("Telemetry output file path is a directory")]
TelemetryOutFileIsDir,
#[error("Torii and Network addresses are the same, but should be different")]
SameNetworkAndToriiAddrs,
#[error("Invalid directory path found")]
InvalidDirPath,
#[error("Cannot bind a listener to address `{addr}`")]
CannotBindAddress { addr: SocketAddr },
}

fn validate_config(config: &Config, submit_genesis: bool) -> Result<(), ConfigValidateError> {
fn validate_config(config: &Config, submit_genesis: bool) -> Result<(), ConfigError> {
let mut emitter = Emitter::new();

validate_try_bind_address(&mut emitter, &config.network.address);
Expand All @@ -659,19 +660,19 @@ fn validate_config(config: &Config, submit_genesis: bool) -> Result<(), ConfigVa
// maybe validate only if snapshot mode is enabled
validate_directory_path(&mut emitter, &config.snapshot.store_dir);

if !submit_genesis && config.sumeragi.trusted_peers.is_empty() {
emitter.emit(Report::new(ConfigValidateError::LonePeer).attach_printable("\
The network consists from this one peer only (no `sumeragi.trusted_peers` provided).\n\
if !submit_genesis && !config.sumeragi.contains_other_trusted_peers() {
emitter.emit(Report::new(ConfigError::LonePeer).attach_printable("\
Reason: the network consists from this one peer only (no `sumeragi.trusted_peers` provided).\n\
Since `--submit-genesis` is not set, there is no way to receive the genesis block.\n\
Either provide the genesis by setting `--submit-genesis` argument, `genesis.private_key`,\n\
and `genesis.file` configuration parameters, or increase the number of trusted peers in\n\
the network using `sumeragi.trusted_peers` configuration parameter.\
"));
").attach_printable(config.sumeragi.trusted_peers.clone().into_attachment().display_as_debug()));
}

if config.network.address.value() == config.torii.address.value() {
emitter.emit(
Report::new(ConfigValidateError::SameNetworkAndToriiAddrs)
Report::new(ConfigError::SameNetworkAndToriiAddrs)
.attach_printable(config.network.address.clone().into_attachment())
.attach_printable(config.torii.address.clone().into_attachment()),
);
Expand All @@ -695,13 +696,13 @@ fn validate_config(config: &Config, submit_genesis: bool) -> Result<(), ConfigVa
if let Some(path) = &config.dev_telemetry.out_file {
if path.value().parent().is_none() {
emitter.emit(
Report::new(ConfigValidateError::TelemetryOutFileIsRootOrEmpty)
Report::new(ConfigError::TelemetryOutFileIsRootOrEmpty)
.attach_printable(path.as_attachment()),
);
}
if path.value().is_dir() {
emitter.emit(
Report::new(ConfigValidateError::TelemetryOutFileIsDir)
Report::new(ConfigError::TelemetryOutFileIsDir)
.attach_printable(path.as_attachment()),
);
}
Expand All @@ -712,7 +713,7 @@ fn validate_config(config: &Config, submit_genesis: bool) -> Result<(), ConfigVa
Ok(())
}

fn validate_directory_path(emitter: &mut Emitter<ConfigValidateError>, path: &WithOrigin<PathBuf>) {
fn validate_directory_path(emitter: &mut Emitter<ConfigError>, path: &WithOrigin<PathBuf>) {
#[derive(Debug, Error)]
#[error(
"expected path to be either non-existing or a directory, but it points to an existing file: {path}"
Expand All @@ -727,22 +728,19 @@ fn validate_directory_path(emitter: &mut Emitter<ConfigValidateError>, path: &Wi
path: path.value().clone(),
})
.attach_printable(path.clone().into_attachment().display_path())
.change_context(ConfigValidateError::InvalidDirPath),
.change_context(ConfigError::InvalidDirPath),
);
}
}

fn validate_try_bind_address(
emitter: &mut Emitter<ConfigValidateError>,
value: &WithOrigin<SocketAddr>,
) {
fn validate_try_bind_address(emitter: &mut Emitter<ConfigError>, value: &WithOrigin<SocketAddr>) {
use std::net::TcpListener;

if let Err(err) = TcpListener::bind(value.value()) {
emitter.emit(
Report::new(err)
.attach_printable(value.clone().into_attachment())
.change_context(ConfigValidateError::CannotBindAddress {
.change_context(ConfigError::CannotBindAddress {
addr: value.value().clone(),
}),
)
Expand Down
16 changes: 12 additions & 4 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ use iroha::Args;

#[derive(thiserror::Error, Debug)]
enum MainError {
#[error("Could not set up configuration tracing (enabled with `--trace-config` CLI argument)")]
#[error("Could not set up configuration tracing")]
TraceConfigSetup,
#[error("Could not start Iroha due to configuration problems")]
#[error("Configuration error")]
Config,
#[error("Could not initialize logger")]
Logger,
Expand All @@ -22,7 +22,9 @@ async fn main() -> error_stack::Result<(), MainError> {
let args = Args::parse();

if args.trace_config {
iroha_config::enable_tracing().change_context(MainError::TraceConfigSetup)?;
iroha_config::enable_tracing()
.change_context(MainError::TraceConfigSetup)
.attach_printable("was enabled by `--trace-config` argument")?;
}

error_stack::Report::set_color_mode(if args.terminal_colors {
Expand All @@ -32,7 +34,13 @@ async fn main() -> error_stack::Result<(), MainError> {
});

let (config, logger_config, genesis) =
iroha::read_config_and_genesis(&args).change_context(MainError::Config)?;
iroha::read_config_and_genesis(&args).change_context(MainError::Config).attach_printable_lazy(|| {
if let Some(path) = &args.config {
format!("config path is specified by `--config` arg: {}", path.display())
} else {
"`--config` arg was not set, therefore configuration relies fully on environment variables".to_owned()
}
})?;
let logger = iroha_logger::init_global(logger_config)
.into_report()
// https://github.com/hashintel/hash/issues/4295
Expand Down
50 changes: 28 additions & 22 deletions config/base/src/attach.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ where
}
}

impl<T> ConfigValueAndOrigin<T> {
pub fn new(value: T, origin: ParameterOrigin) -> Self {
impl<T, F> ConfigValueAndOrigin<T, F> {
fn new_internal(value: T, origin: ParameterOrigin) -> Self {
Self {
value,
origin,
Expand All @@ -83,28 +83,30 @@ impl<T> ConfigValueAndOrigin<T> {
}
}

impl<T: AsRef<Path>> ConfigValueAndOrigin<T> {
// pub fn map<F, U>(self, fun: F) -> ConfigValueAndOrigin<U>
// where
// F: FnOnce(T) -> U,
// {
// let Self { value, origin } = self;
// ConfigValueAndOrigin {
// value: fun(value),
// origin,
// }
// }
impl<T> ConfigValueAndOrigin<T> {
pub fn new(value: T, origin: ParameterOrigin) -> Self {
ConfigValueAndOrigin::new_internal(value, origin)
}
}

impl<T: AsRef<Path>> ConfigValueAndOrigin<T> {
pub fn display_path(self) -> ConfigValueAndOrigin<T, FormatPath<T>> {
let Self { value, origin, .. } = self;
ConfigValueAndOrigin {
value,
origin,
_f: PhantomData,
}
ConfigValueAndOrigin::new_internal(self.value, self.origin)
}
}

impl<T: Debug> ConfigValueAndOrigin<T> {
pub fn display_as_debug(self) -> ConfigValueAndOrigin<T, FormatDebug<T>> {
ConfigValueAndOrigin::new_internal(self.value, self.origin)
}
}

pub trait DisplayProxy {
type Base: ?Sized;

fn fmt(value: &Self::Base, f: &mut Formatter<'_>) -> std::fmt::Result;
}

pub struct FormatDisplay<T>(PhantomData<T>);

impl<T: Display> DisplayProxy for FormatDisplay<T> {
Expand All @@ -126,10 +128,14 @@ impl<T: AsRef<Path>> DisplayProxy for FormatPath<T> {
}
}

pub trait DisplayProxy {
type Base: ?Sized;
pub struct FormatDebug<T>(PhantomData<T>);

fn fmt(value: &Self::Base, f: &mut Formatter<'_>) -> std::fmt::Result;
impl<T: Debug> DisplayProxy for FormatDebug<T> {
type Base = T;

fn fmt(value: &Self::Base, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{value:?}")
}
}

struct DisplayWithProxy<'a, T, F>(&'a T, PhantomData<F>);
Expand Down
11 changes: 11 additions & 0 deletions config/base/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,17 @@ impl<T> WithOrigin<T> {
pub fn into_attachment(self) -> ConfigValueAndOrigin<T> {
ConfigValueAndOrigin::new(self.value, self.origin)
}

pub fn map<F, U>(self, fun: F) -> WithOrigin<U>
where
F: FnOnce(T) -> U,
{
let Self { value, origin } = self;
WithOrigin {
value: fun(value),
origin,
}
}
}

impl WithOrigin<PathBuf> {
Expand Down
10 changes: 9 additions & 1 deletion config/src/parameters/actual.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,18 @@ impl Default for Queue {
#[derive(Debug, Clone)]
#[allow(missing_docs)]
pub struct Sumeragi {
pub trusted_peers: UniqueVec<PeerId>,
/// **Must** contain id of the peer itself
pub trusted_peers: WithOrigin<UniqueVec<PeerId>>,
pub debug_force_soft_fork: bool,
}

impl Sumeragi {
/// Tells whether a trusted peers list has some other peers except for the peer itself
pub fn contains_other_trusted_peers(&self) -> bool {
self.trusted_peers.value().len() > 1
}
}

#[derive(Debug, Clone, Copy)]
#[allow(missing_docs)]
pub struct LiveQueryStore {
Expand Down
Loading

0 comments on commit 786b8c6

Please sign in to comment.