Skip to content

Commit

Permalink
[feature] #2373: kagami swarm (#3475)
Browse files Browse the repository at this point in the history
* [refactor]: bump `clap`, scaffold `swarm` subcommand

Signed-off-by: Dmitry Balashov <a.marcius26@gmail.com>

* [refactor]: transform `algorithm` to `ValueEnum`

Signed-off-by: Dmitry Balashov <a.marcius26@gmail.com>

* [feature]: implement basic flow

Signed-off-by: Dmitry Balashov <a.marcius26@gmail.com>

* [chore]: clear dead code

Signed-off-by: Dmitry Balashov <a.marcius26@gmail.com>

* [refactor]: safer paths manipulations

Signed-off-by: Dmitry Balashov <a.marcius26@gmail.com>

* [fix]: sort generated services

Signed-off-by: Dmitry Balashov <a.marcius26@gmail.com>

* [test]: update snapshot

Signed-off-by: Dmitry Balashov <a.marcius26@gmail.com>

* [fix]: skip empty command serialisation; chores

Signed-off-by: Dmitry Balashov <a.marcius26@gmail.com>

* [refactor]: define consts

Signed-off-by: Dmitry Balashov <a.marcius26@gmail.com>

* [refactor]: pass workspace check; lints

Signed-off-by: Dmitry Balashov <a.marcius26@gmail.com>

* [refactor]: chores

Signed-off-by: Dmitry Balashov <a.marcius26@gmail.com>

* [feat]: impl basic UI

Signed-off-by: Dmitry Balashov <a.marcius26@gmail.com>

* [fix]: fix shallow git clone

Signed-off-by: Dmitry Balashov <a.marcius26@gmail.com>

* [chore]: update ui

Signed-off-by: Dmitry Balashov <a.marcius26@gmail.com>

* [refactor]: simplify resolution reporting

Signed-off-by: Dmitry Balashov <a.marcius26@gmail.com>

* [refactor]: doc comments, naming, etc

Signed-off-by: Dmitry Balashov <a.marcius26@gmail.com>

* [refactor]: move `swarm` mod

Signed-off-by: Dmitry Balashov <a.marcius26@gmail.com>

* [refactor]: chores; update doc comments

Signed-off-by: Dmitry Balashov <a.marcius26@gmail.com>

* [fix]: fix config path in docker image

Signed-off-by: Dmitry Balashov <a.marcius26@gmail.com>

* [fix]: clippy lints

Signed-off-by: Dmitry Balashov <a.marcius26@gmail.com>

* [fix]: specify all trusted peers; set genesis key pair only for the genesis peer

Signed-off-by: Dmitry Balashov <a.marcius26@gmail.com>

* [chore]: add fixme

Signed-off-by: Dmitry Balashov <a.marcius26@gmail.com>

* [refactor]: chores

Signed-off-by: Dmitry Balashov <a.marcius26@gmail.com>

* [test]: fix `kagami` and `iroha_config_base` tests

Signed-off-by: Dmitry Balashov <a.marcius26@gmail.com>

* [fix]: use `SocketAddr`

Signed-off-by: Dmitry Balashov <a.marcius26@gmail.com>

* [refactor]: apply lints

Signed-off-by: Dmitry Balashov <a.marcius26@gmail.com>

* [refactor]: allow any docker image; rename args

Signed-off-by: Dmitry Balashov <a.marcius26@gmail.com>

* [refactor]: fix lints

Signed-off-by: Dmitry Balashov <a.marcius26@gmail.com>

* Apply suggestions from code review

Co-authored-by: Ilia Churin <churin.ilya@gmail.com>
Signed-off-by: 0x009922 <a.marcius26@gmail.com>

* [chore]: remove extra doc

Signed-off-by: Dmitry Balashov <a.marcius26@gmail.com>

* [chore]: remove inaccurate comment

Signed-off-by: Dmitry Balashov <a.marcius26@gmail.com>

* [refactor]: use `.dir()`

Signed-off-by: Dmitry Balashov <a.marcius26@gmail.com>

* [refactor]: derive `Display` for `AlgorithmArg`

Signed-off-by: Dmitry Balashov <a.marcius26@gmail.com>

* [refactor]: use `Error::FieldDeserialization`

Signed-off-by: Dmitry Balashov <a.marcius26@gmail.com>

* [refactor]: apply lints

Signed-off-by: Dmitry Balashov <a.marcius26@gmail.com>

* [refactor]: move secp256k1 seed len check to `iroha_crypto`

Signed-off-by: Dmitry Balashov <a.marcius26@gmail.com>

* [refactor]: expand `swarm::key_gen` module

Signed-off-by: Dmitry Balashov <a.marcius26@gmail.com>

* [chore]: fix format

Signed-off-by: Dmitry Balashov <a.marcius26@gmail.com>

* [chore]: capitalise error messages

Signed-off-by: Dmitry Balashov <a.marcius26@gmail.com>

---------

Signed-off-by: Dmitry Balashov <a.marcius26@gmail.com>
Signed-off-by: 0x009922 <a.marcius26@gmail.com>
Co-authored-by: Ilia Churin <churin.ilya@gmail.com>
  • Loading branch information
2 people authored and mversic committed Oct 17, 2023
1 parent 0cebc16 commit 76828f8
Show file tree
Hide file tree
Showing 17 changed files with 2,149 additions and 429 deletions.
785 changes: 486 additions & 299 deletions Cargo.lock

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,10 @@ pub fn combine_configs(args: &Arguments) -> color_eyre::eyre::Result<Configurati
ConfigurationProxy::default().override_with(path_proxy)
},
)
.override_with(ConfigurationProxy::from_env())
.override_with(
ConfigurationProxy::from_std_env()
.wrap_err("Failed to build configuration from env")?,
)
.build()
.map_err(Into::into)
}
Expand Down
3 changes: 3 additions & 0 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ async fn main() -> Result<(), color_eyre::Report> {
let config = iroha::combine_configs(&args)?;
let telemetry = iroha_logger::init(&config.logger)?;
if !config.disable_panic_terminal_colors {
// FIXME: it shouldn't be logged here; it is a part of configuration domain
// this message can be very simply broken by the changes in the configuration
// https://github.com/hyperledger/iroha/issues/3506
iroha_logger::warn!("The configuration parameter `DISABLE_PANIC_TERMINAL_COLORS` is deprecated. Set `TERMINAL_COLORS=false` instead. ")
}
iroha_logger::info!(
Expand Down
2 changes: 2 additions & 0 deletions config/base/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,5 @@ serde_json = "1.0.91"
json5 = "0.4.1"
thiserror = "1.0.38"
crossbeam = "0.8.2"
eyre = "0.6.8"

8 changes: 4 additions & 4 deletions config/base/derive/src/documented.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,10 +195,10 @@ fn impl_get_recursive(ast: &StructWithFields) -> proc_macro2::TokenStream {
serde_json::to_value(&#l_value)
.map_err(
|error|
::iroha_config_base::derive::Error::FieldDeserialization {
field: stringify!(#ident),
error
}
::iroha_config_base::derive::Error::field_deserialization_from_json(
stringify!(#ident),
&error
)
)?
}
#inner_thing2
Expand Down
42 changes: 26 additions & 16 deletions config/base/derive/src/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ pub fn impl_override(ast: &StructWithFields) -> TokenStream {

#[allow(clippy::str_to_string)]
pub fn impl_load_from_env(ast: &StructWithFields) -> TokenStream {
let env_fetcher_ident = quote! { env_fetcher };
let fetch_env_trait = quote! { ::iroha_config_base::proxy::FetchEnv };
let env_trait = quote! { ::iroha_config_base::proxy::LoadFromEnv };

let set_field = ast.fields
.iter()
.map(|field| {
Expand All @@ -91,37 +95,43 @@ pub fn impl_load_from_env(ast: &StructWithFields) -> TokenStream {
} else {
false
};
let err_ty = quote! { ::iroha_config_base::derive::Error };
let err_variant = quote! { ::iroha_config_base::derive::Error::Json5 };
let inner = if is_string {
quote! { Ok(var) }
} else if as_str_attr {
quote! {{
let value: ::serde_json::Value = var.into();
::json5::from_str(&value.to_string()).map_err(#err_variant)
::json5::from_str(&value.to_string())
}}
} else {
quote! { ::json5::from_str(&var).map_err(#err_variant) }
quote! { ::json5::from_str(&var) }
};
let mut set_field = quote! {
let #ident = std::env::var(#field_env)
let #ident = #env_fetcher_ident.fetch(#field_env)
// treating unicode errors the same as variable absence
.ok()
.and_then(|var| {
let inner: Result<#inner_ty, #err_ty> = #inner;
inner.ok()
});
.map(|var| {
#inner.map_err(|err| {
::iroha_config_base::derive::Error::field_deserialization_from_json5(
// FIXME: specify location precisely
// https://github.com/hyperledger/iroha/issues/3470
#field_env,
&err
)
})
})
.transpose()?;
};
if field.has_inner {
set_field.extend(quote! {
let inner_proxy = <#inner_ty as ::iroha_config_base::proxy::LoadFromEnv>::from_env();
let inner_proxy = <#inner_ty as #env_trait>::from_env(#env_fetcher_ident)?;
let #ident = if let Some(old_inner) = #ident {
Some(<#inner_ty as ::iroha_config_base::proxy::Override>::override_with(old_inner, inner_proxy))
} else {
Some(inner_proxy)
};
});
}
set_field
set_field
});

let name = &ast.ident;
Expand All @@ -133,15 +143,15 @@ pub fn impl_load_from_env(ast: &StructWithFields) -> TokenStream {
quote! { #ident }
})
.collect::<Vec<_>>();
let env_trait = quote! { ::iroha_config_base::proxy::LoadFromEnv };
quote! {
impl #env_trait for #name {
type ReturnValue = Self;
fn from_env() -> Self::ReturnValue {
type ReturnValue = Result<Self, ::iroha_config_base::derive::Error>;
fn from_env<F: #fetch_env_trait>(#env_fetcher_ident: &F) -> Self::ReturnValue {
#(#set_field)*
#name {
let proxy = #name {
#(#fields),*
}
};
Ok(proxy)
}
}
}
Expand Down
67 changes: 58 additions & 9 deletions config/base/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ pub mod derive {
/// struct PrefixedProxy { a: Option<String> }
///
/// std::env::set_var("PREFIXED_A", "B");
/// let prefixed = PrefixedProxy::from_env();
/// let prefixed = PrefixedProxy::from_std_env().unwrap();
/// assert_eq!(prefixed.a.unwrap(), "B");
/// ```
///
Expand All @@ -196,7 +196,7 @@ pub mod derive {
/// let mut outer = OuterProxy { inner: Some(InnerProxy { b: Some("a".to_owned()) })};
///
/// std::env::set_var("B", "a");
/// let env_outer = OuterProxy::from_env();
/// let env_outer = OuterProxy::from_std_env().unwrap();
///
/// assert_eq!(env_outer, outer);
/// ```
Expand All @@ -215,7 +215,7 @@ pub mod derive {
/// struct IpAddrProxy { #[config(serde_as_str)] ip: Option<Ipv4Addr> }
///
/// std::env::set_var("IP", "127.0.0.1");
/// let ip = IpAddrProxy::from_env();
/// let ip = IpAddrProxy::from_std_env().unwrap();
/// assert_eq!(ip.ip.unwrap(), Ipv4Addr::new(127, 0, 0, 1));
/// ```
pub use iroha_config_derive::LoadFromEnv;
Expand Down Expand Up @@ -313,16 +313,16 @@ pub mod derive {
#[error("Got unknown field: `{}`", .0.join("."))]
UnknownField(Vec<String>),

/// Used in [`Documented`] trait for deserialization errors
/// while retaining field info
#[error("Failed to (de)serialize the field: {}", .field)]
/// Used in [`Documented`] and [`super::proxy::LoadFromEnv`] trait for deserialization
/// errors
#[error("Failed to deserialize the field `{}`: {}", .field, .error)]
#[serde(skip)]
FieldDeserialization {
/// Field name (known at compile time)
field: &'static str,
/// Serde json error
/// Unified error
#[source]
error: serde_json::Error,
error: eyre::Report,
},

/// When a field is missing.
Expand Down Expand Up @@ -373,6 +373,28 @@ pub mod derive {
#[serde(skip)]
Json5(#[from] json5::Error),
}

impl Error {
/// This method is needed because a call of [`eyre::eyre!`] cannot be compiled when
/// generated in a proc macro. So, this shorthand is needed for proc macros.
pub fn field_deserialization_from_json(
field: &'static str,
error: &serde_json::Error,
) -> Self {
Self::FieldDeserialization {
field,
error: eyre::eyre!("JSON: {}", error),
}
}

/// See [`Self::field_deserialization_from_json`]
pub fn field_deserialization_from_json5(field: &'static str, error: &json5::Error) -> Self {
Self::FieldDeserialization {
field,
error: eyre::eyre!("JSON5: {}", error),
}
}
}
}

pub mod runtime_upgrades;
Expand Down Expand Up @@ -478,7 +500,34 @@ pub mod proxy {
///
/// # Errors
/// - Fails if the deserialization of any field fails.
fn from_env() -> Self::ReturnValue;
fn from_env<F: FetchEnv>(fetcher: &F) -> Self::ReturnValue;

/// Implementation of [`Self::from_env`] using [`std::env::var`].
fn from_std_env() -> Self::ReturnValue {
struct FetchStdEnv;

impl FetchEnv for FetchStdEnv {
fn fetch<K: AsRef<std::ffi::OsStr>>(
&self,
key: K,
) -> Result<String, std::env::VarError> {
std::env::var(key)
}
}

Self::from_env(&FetchStdEnv)
}
}

/// Abstraction over the actual implementation of how env variables are gotten
/// from the environment. Necessary for mocking in tests.
pub trait FetchEnv {
/// The signature of [`std::env::var`].
///
/// # Errors
///
/// See errors of [`std::env::var`].
fn fetch<K: AsRef<std::ffi::OsStr>>(&self, key: K) -> Result<String, std::env::VarError>;
}

/// Trait for configuration loading and deserialization from disk
Expand Down
100 changes: 80 additions & 20 deletions config/base/tests/simple.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#![allow(clippy::restriction)]

use std::{collections::HashMap, env::VarError, ffi::OsStr};

use iroha_config_base::{
derive::{Documented, LoadFromEnv, Override},
proxy::{Documented as _, LoadFromEnv as _, Override as _},
proxy::{Documented as _, FetchEnv, LoadFromEnv as _, Override as _},
};
use serde::{Deserialize, Serialize};

Expand Down Expand Up @@ -101,27 +103,62 @@ fn test_docs() {
);
}

fn env_var_setup() {
struct TestEnv {
map: HashMap<String, String>,
}

impl TestEnv {
fn new() -> Self {
Self {
map: HashMap::new(),
}
}

fn set_var(&mut self, key: impl AsRef<str>, value: impl AsRef<str>) {
self.map
.insert(key.as_ref().to_owned(), value.as_ref().to_owned());
}

fn remove_var(&mut self, key: impl AsRef<str>) {
self.map.remove(key.as_ref());
}
}

impl FetchEnv for TestEnv {
fn fetch<K: AsRef<OsStr>>(&self, key: K) -> Result<String, VarError> {
self.map
.get(
key.as_ref()
.to_str()
.ok_or_else(|| VarError::NotUnicode(key.as_ref().to_owned()))?,
)
.ok_or(VarError::NotPresent)
.map(Clone::clone)
}
}

fn test_env_factory() -> TestEnv {
let string_wrapper_json = "string";
let string = "cool string";
let data_json = "{\"key\": \"key\", \"value\": 34}";
let inner_json = "{\"a\": \"\", \"b\": 0}";
std::env::set_var("CONF_STRING_WRAPPER", string_wrapper_json);
std::env::set_var("CONF_STRING", string);
std::env::set_var("CONF_DATA", data_json);
std::env::set_var("CONF_OPTIONAL_STRING_WRAPPER", string_wrapper_json);
std::env::set_var("CONF_OPTIONAL_STRING", string);
std::env::set_var("CONF_OPTIONAL_DATA", data_json);
std::env::set_var("CONF_OPTIONAL_INNER", inner_json);
std::env::set_var("CONF_INNER_A", "string");
std::env::set_var("CONF_INNER_B", "42");
let data_json = r#"{"key": "key", "value": 34}"#;
let inner_json = r#"{"a": "", "b": 0}"#;
let mut env = TestEnv::new();
env.set_var("CONF_STRING_WRAPPER", string_wrapper_json);
env.set_var("CONF_STRING", string);
env.set_var("CONF_DATA", data_json);
env.set_var("CONF_OPTIONAL_STRING_WRAPPER", string_wrapper_json);
env.set_var("CONF_OPTIONAL_STRING", string);
env.set_var("CONF_OPTIONAL_DATA", data_json);
env.set_var("CONF_OPTIONAL_INNER", inner_json);
env.set_var("CONF_INNER_A", "string");
env.set_var("CONF_INNER_B", "42");
env
}

#[test]
fn test_proxy_load_from_env() {
env_var_setup();
let config = ConfigurationProxy::new_with_placeholders();
let env_config = ConfigurationProxy::from_env();
let env_config = ConfigurationProxy::from_env(&test_env_factory()).expect("valid env");
assert_eq!(&env_config.optional_data, &config.optional_data);
assert_eq!(
&env_config.optional_string_wrapper,
Expand All @@ -133,21 +170,44 @@ fn test_proxy_load_from_env() {

#[test]
fn test_can_load_inner_without_the_wrapping_config() {
env_var_setup();
std::env::remove_var("CONF_OPTIONAL_INNER");
let mut env = test_env_factory();
env.remove_var("CONF_OPTIONAL_INNER");
let config = ConfigurationProxy::new_with_placeholders();
let env_config = ConfigurationProxy::from_env();
let env_config = ConfigurationProxy::from_env(&env).expect("valid env");
assert_eq!(&env_config.optional_inner, &config.optional_inner)
}

#[test]
fn test_proxy_combine_does_not_overload_with_none() {
env_var_setup();
let config = ConfigurationProxy::new_with_none();
dbg!(&config);
let env_config = ConfigurationProxy::from_env();
let env_config = ConfigurationProxy::from_env(&test_env_factory()).expect("valid env");
dbg!(&env_config);
let combine_config = env_config.clone().override_with(config);
dbg!(&combine_config);
assert_eq!(&env_config.optional_data, &combine_config.optional_data);
}

#[test]
fn configuration_proxy_from_env_returns_err_on_parsing_error() {
#[derive(LoadFromEnv, Debug)]
#[config(env_prefix = "")]
struct Target {
#[allow(dead_code)]
foo: Option<u64>,
}

struct Env;

impl FetchEnv for Env {
fn fetch<K: AsRef<OsStr>>(&self, key: K) -> Result<String, VarError> {
match key.as_ref().to_str().unwrap() {
"FOO" => Ok("not u64 for sure".to_owned()),
_ => Err(VarError::NotPresent),
}
}
}

let err = Target::from_env(&Env).expect_err("Must not be parsed");
assert_eq!(format!("{err}"), "Failed to deserialize env var `FOO` as JSON5: --> 1:1\n |\n1 | not u64 for sure\n | ^---\n |\n = expected array, boolean, null, number, object, or string");
}
2 changes: 1 addition & 1 deletion config/src/genesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ pub mod tests {
fn placeholder_keypair() -> KeyPair {
let public_key = "ed01204CFFD0EE429B1BDD36B3910EC570852B8BB63F18750341772FB46BC856C5CAAF"
.parse()
.expect("Public key not in mulithash format");
.expect("Public key not in multihash format");
let private_key = PrivateKey::from_hex(
iroha_crypto::Algorithm::Ed25519,
"D748E18CE60CB30DEA3E73C9019B7AF45A8D465E3D71BCC9A5EF99A008205E534CFFD0EE429B1BDD36B3910EC570852B8BB63F18750341772FB46BC856C5CAAF".as_ref()
Expand Down
1 change: 1 addition & 0 deletions config/src/iroha.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! This module contains [`struct@Configuration`] structure and related implementation.
#![allow(clippy::std_instead_of_core)]

use std::fmt::Debug;

use iroha_config_base::derive::{view, Documented, Error as ConfigError, Proxy};
Expand Down
Loading

0 comments on commit 76828f8

Please # to comment.