forked from hyperledger-iroha/iroha
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[fix] hyperledger-iroha#3473: Make
kagami validator
runnable from o…
…utside `iroha` dir Signed-off-by: Ilia Churin <churin.ilya@gmail.com>
- Loading branch information
Showing
14 changed files
with
745 additions
and
682 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
use std::str::FromStr as _; | ||
|
||
use clap::{Parser, Subcommand}; | ||
use iroha_crypto::{Algorithm, PrivateKey, PublicKey}; | ||
use iroha_primitives::small::SmallStr; | ||
|
||
use super::*; | ||
|
||
#[derive(Parser, Debug, Clone, Copy)] | ||
pub struct Args { | ||
#[clap(subcommand)] | ||
mode: Mode, | ||
} | ||
|
||
#[derive(Subcommand, Debug, Clone, Copy)] | ||
pub enum Mode { | ||
Client(client::Args), | ||
Peer(peer::Args), | ||
} | ||
|
||
impl<T: Write> RunArgs<T> for Args { | ||
fn run(self, writer: &mut BufWriter<T>) -> Outcome { | ||
match self.mode { | ||
Mode::Client(args) => args.run(writer), | ||
Mode::Peer(args) => args.run(writer), | ||
} | ||
} | ||
} | ||
|
||
mod client { | ||
use iroha_config::{ | ||
client::{BasicAuth, ConfigurationProxy, WebLogin}, | ||
torii::{uri::DEFAULT_API_ADDR, DEFAULT_TORII_TELEMETRY_ADDR}, | ||
}; | ||
|
||
use super::*; | ||
|
||
#[derive(ClapArgs, Debug, Clone, Copy)] | ||
pub struct Args; | ||
|
||
impl<T: Write> RunArgs<T> for Args { | ||
fn run(self, writer: &mut BufWriter<T>) -> Outcome { | ||
let config = ConfigurationProxy { | ||
torii_api_url: Some(format!("http://{DEFAULT_API_ADDR}").parse()?), | ||
torii_telemetry_url: Some(format!("http://{DEFAULT_TORII_TELEMETRY_ADDR}").parse()?), | ||
account_id: Some("alice@wonderland".parse()?), | ||
basic_auth: Some(Some(BasicAuth { | ||
web_login: WebLogin::new("mad_hatter")?, | ||
password: SmallStr::from_str("ilovetea"), | ||
})), | ||
public_key: Some(PublicKey::from_str( | ||
"ed01207233BFC89DCBD68C19FDE6CE6158225298EC1131B6A130D1AEB454C1AB5183C0", | ||
)?), | ||
private_key: Some(PrivateKey::from_hex( | ||
Algorithm::Ed25519, | ||
"9AC47ABF59B356E0BD7DCBBBB4DEC080E302156A48CA907E47CB6AEA1D32719E7233BFC89DCBD68C19FDE6CE6158225298EC1131B6A130D1AEB454C1AB5183C0".as_ref() | ||
)?), | ||
..ConfigurationProxy::default() | ||
} | ||
.build()?; | ||
writeln!(writer, "{}", serde_json::to_string_pretty(&config)?) | ||
.wrap_err("Failed to write serialized client configuration to the buffer.") | ||
} | ||
} | ||
} | ||
|
||
mod peer { | ||
use iroha_config::iroha::ConfigurationProxy as IrohaConfigurationProxy; | ||
|
||
use super::*; | ||
|
||
#[derive(ClapArgs, Debug, Clone, Copy)] | ||
pub struct Args; | ||
|
||
impl<T: Write> RunArgs<T> for Args { | ||
fn run(self, writer: &mut BufWriter<T>) -> Outcome { | ||
let config = IrohaConfigurationProxy::default(); | ||
writeln!(writer, "{}", serde_json::to_string_pretty(&config)?) | ||
.wrap_err("Failed to write serialized peer configuration to the buffer.") | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
use clap::{builder::PossibleValue, ArgGroup, ValueEnum}; | ||
use color_eyre::eyre::WrapErr as _; | ||
use iroha_crypto::{Algorithm, KeyGenConfiguration, KeyPair, PrivateKey}; | ||
|
||
use super::*; | ||
|
||
/// Use `Kagami` to generate cryptographic key-pairs. | ||
#[derive(ClapArgs, Debug, Clone)] | ||
#[command(group = ArgGroup::new("generate_from").required(false))] | ||
#[command(group = ArgGroup::new("format").required(false))] | ||
pub struct Args { | ||
/// The algorithm to use for the key-pair generation | ||
#[clap(default_value_t, long, short)] | ||
algorithm: AlgorithmArg, | ||
/// The `private_key` to generate the key-pair from | ||
#[clap(long, short, group = "generate_from")] | ||
private_key: Option<String>, | ||
/// The `seed` to generate the key-pair from | ||
#[clap(long, short, group = "generate_from")] | ||
seed: Option<String>, | ||
/// Output the key-pair in JSON format | ||
#[clap(long, short, group = "format")] | ||
json: bool, | ||
/// Output the key-pair without additional text | ||
#[clap(long, short, group = "format")] | ||
compact: bool, | ||
} | ||
|
||
#[derive(Clone, Debug, Default, derive_more::Display)] | ||
struct AlgorithmArg(Algorithm); | ||
|
||
impl ValueEnum for AlgorithmArg { | ||
fn value_variants<'a>() -> &'a [Self] { | ||
// TODO: add compile-time check to ensure all variants are enumerated | ||
&[ | ||
Self(Algorithm::Ed25519), | ||
Self(Algorithm::Secp256k1), | ||
Self(Algorithm::BlsNormal), | ||
Self(Algorithm::BlsSmall), | ||
] | ||
} | ||
|
||
fn to_possible_value(&self) -> Option<PossibleValue> { | ||
Some(self.0.as_static_str().into()) | ||
} | ||
} | ||
|
||
impl<T: Write> RunArgs<T> for Args { | ||
fn run(self, writer: &mut BufWriter<T>) -> Outcome { | ||
if self.json { | ||
let key_pair = self.key_pair()?; | ||
let output = | ||
serde_json::to_string_pretty(&key_pair).wrap_err("Failed to serialise to JSON.")?; | ||
writeln!(writer, "{output}")?; | ||
} else if self.compact { | ||
let key_pair = self.key_pair()?; | ||
writeln!(writer, "{}", &key_pair.public_key())?; | ||
writeln!(writer, "{}", &key_pair.private_key())?; | ||
writeln!(writer, "{}", &key_pair.public_key().digest_function())?; | ||
} else { | ||
let key_pair = self.key_pair()?; | ||
writeln!( | ||
writer, | ||
"Public key (multihash): \"{}\"", | ||
&key_pair.public_key() | ||
)?; | ||
writeln!( | ||
writer, | ||
"Private key ({}): \"{}\"", | ||
&key_pair.public_key().digest_function(), | ||
&key_pair.private_key() | ||
)?; | ||
} | ||
Ok(()) | ||
} | ||
} | ||
|
||
impl Args { | ||
fn key_pair(self) -> color_eyre::Result<KeyPair> { | ||
let algorithm = self.algorithm.0; | ||
let config = KeyGenConfiguration::default().with_algorithm(algorithm); | ||
|
||
let key_pair = match (self.seed, self.private_key) { | ||
(None, None) => KeyPair::generate_with_configuration(config), | ||
(None, Some(private_key_hex)) => { | ||
let private_key = PrivateKey::from_hex(algorithm, private_key_hex.as_ref()) | ||
.wrap_err("Failed to decode private key")?; | ||
KeyPair::generate_with_configuration(config.use_private_key(private_key)) | ||
} | ||
(Some(seed), None) => { | ||
let seed: Vec<u8> = seed.as_bytes().into(); | ||
KeyPair::generate_with_configuration(config.use_seed(seed)) | ||
} | ||
_ => unreachable!("Clap group invariant"), | ||
} | ||
.wrap_err("Failed to generate key pair")?; | ||
|
||
Ok(key_pair) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::{Algorithm, AlgorithmArg}; | ||
|
||
#[test] | ||
fn algorithm_arg_displays_as_algorithm() { | ||
assert_eq!( | ||
format!("{}", AlgorithmArg(Algorithm::Ed25519)), | ||
format!("{}", Algorithm::Ed25519) | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
#![allow(clippy::panic_in_result_fn, clippy::expect_used)] | ||
#![allow( | ||
clippy::arithmetic_side_effects, | ||
clippy::std_instead_of_core, | ||
clippy::std_instead_of_alloc | ||
)] | ||
use std::{fmt::Debug, io::Write}; | ||
|
||
use color_eyre::eyre::WrapErr as _; | ||
use iroha_config::{base::proxy::Documented, iroha::ConfigurationProxy}; | ||
use serde_json::Value; | ||
|
||
use super::*; | ||
|
||
impl<E: Debug, C: Documented<Error = E> + Send + Sync + Default> PrintDocs for C {} | ||
|
||
#[derive(ClapArgs, Debug, Clone, Copy)] | ||
pub struct Args; | ||
|
||
impl<T: Write> RunArgs<T> for Args { | ||
fn run(self, writer: &mut BufWriter<T>) -> crate::Outcome { | ||
ConfigurationProxy::get_markdown(writer).wrap_err("Failed to generate documentation") | ||
} | ||
} | ||
|
||
pub trait PrintDocs: Documented + Send + Sync + Default | ||
where | ||
Self::Error: Debug, | ||
{ | ||
fn get_markdown<W: Write>(writer: &mut W) -> color_eyre::Result<()> { | ||
let Value::Object(docs) = Self::get_docs() else { | ||
unreachable!("As top level structure is always object") | ||
}; | ||
let mut vec = Vec::new(); | ||
let defaults = serde_json::to_string_pretty(&Self::default())?; | ||
|
||
writeln!(writer, "# Iroha Configuration reference\n")?; | ||
writeln!(writer, "In this document we provide a reference and detailed descriptions of Iroha's configuration options. \ | ||
The options have different underlying types and default values, which are denoted in code as types wrapped in a single \ | ||
`Option<..>` or in a double `Option<Option<..>>`. For the detailed explanation, please refer to \ | ||
this [section](#configuration-types).\n")?; | ||
writeln!( | ||
writer, | ||
"## Configuration types\n\n\ | ||
### `Option<..>`\n\n\ | ||
A type wrapped in a single `Option<..>` signifies that in the corresponding `json` block there is a fallback value for this type, \ | ||
and that it only serves as a reference. If a default for such a type has a `null` value, it means that there is no meaningful fallback \ | ||
available for this particular value.\n\nAll the default values can be freely obtained from a provided [sample configuration file](../../../configs/peer/config.json), \ | ||
but it should only serve as a starting point. If left unchanged, the sample configuration file would still fail to build due to it having `null` in place of \ | ||
[public](#public_key) and [private](#private_key) keys as well as [endpoint](#torii.api_url) [URLs](#torii.telemetry_url). \ | ||
These should be provided either by modifying the sample config file or as environment variables. \ | ||
No other overloading of configuration values happens besides reading them from a file and capturing the environment variables.\n\n\ | ||
For both types of configuration options wrapped in a single `Option<..>` (i.e. both those that have meaningful defaults and those that have `null`), \ | ||
failure to provide them in any of the above two ways results in an error.\n\n\ | ||
### `Option<Option<..>>`\n\n\ | ||
`Option<Option<..>>` types should be distinguished from types wrapped in a single `Option<..>`. Only the double option ones are allowed to stay `null`, \ | ||
meaning that **not** providing them in an environment variable or a file will **not** result in an error.\n\n\ | ||
Thus, only these types are truly optional in the mundane sense of the word. \ | ||
An example of this distinction is genesis [public](#genesis.account_public_key) and [private](#genesis.account_private_key) key. \ | ||
While the first one is a single `Option<..>` wrapped type, the latter is wrapped in `Option<Option<..>>`. This means that the genesis *public* key should always be \ | ||
provided by the user, be it via a file config or an environment variable, whereas the *private* key is only needed for the peer that submits the genesis block, \ | ||
and can be omitted for all others. The same logic goes for other double option fields such as logger file path.\n\n\ | ||
### Sumeragi: default `null` values\n\n\ | ||
A special note about sumeragi fields with `null` as default: only the [`trusted_peers`](#sumeragi.trusted_peers) field out of the three can be initialized via a \ | ||
provided file or an environment variable.\n\n\ | ||
The other two fields, namely [`key_pair`](#sumeragi.key_pair) and [`peer_id`](#sumeragi.peer_id), go through a process of finalization where their values \ | ||
are derived from the corresponding ones in the uppermost Iroha config (using its [`public_key`](#public_key) and [`private_key`](#private_key) fields) \ | ||
or the Torii config (via its [`p2p_addr`](#torii.p2p_addr)). \ | ||
This ensures that these linked fields stay in sync, and prevents the programmer error when different values are provided to these field pairs. \ | ||
Providing either `sumeragi.key_pair` or `sumeragi.peer_id` by hand will result in an error, as it should never be done directly.\n" | ||
)?; | ||
writeln!(writer, "## Default configuration\n")?; | ||
writeln!( | ||
writer, | ||
"The following is the default configuration used by Iroha.\n" | ||
)?; | ||
writeln!(writer, "```json\n{defaults}\n```\n")?; | ||
Self::get_markdown_with_depth(writer, &docs, &mut vec, 2)?; | ||
Ok(()) | ||
} | ||
|
||
fn get_markdown_with_depth<W: Write>( | ||
writer: &mut W, | ||
docs: &serde_json::Map<String, Value>, | ||
field: &mut Vec<String>, | ||
depth: usize, | ||
) -> color_eyre::Result<()> { | ||
let current_field = { | ||
let mut docs = docs; | ||
for f in &*field { | ||
docs = match &docs[f] { | ||
Value::Object(obj) => obj, | ||
_ => unreachable!(), | ||
}; | ||
} | ||
docs | ||
}; | ||
|
||
for (f, value) in current_field { | ||
field.push(f.clone()); | ||
let get_field = field.iter().map(AsRef::as_ref).collect::<Vec<&str>>(); | ||
let (doc, inner) = match value { | ||
Value::Object(_) => { | ||
let doc = Self::get_doc_recursive(&get_field) | ||
.expect("Should be there, as already in docs"); | ||
(doc.unwrap_or_default(), true) | ||
} | ||
Value::String(s) => (s.clone(), false), | ||
_ => unreachable!("Only strings and objects in docs"), | ||
}; | ||
// Hacky workaround to avoid duplicating inner fields docs in the reference | ||
let doc = doc.lines().take(3).collect::<Vec<&str>>().join("\n"); | ||
let doc = doc.strip_prefix(' ').unwrap_or(&doc); | ||
let defaults = Self::default() | ||
.get_recursive(get_field) | ||
.expect("Failed to get defaults."); | ||
let defaults = serde_json::to_string_pretty(&defaults)?; | ||
let field_str = field | ||
.join(".") | ||
.chars() | ||
.filter(|&chr| chr != ' ') | ||
.collect::<String>(); | ||
|
||
write!(writer, "{} `{}`\n\n", "#".repeat(depth), field_str)?; | ||
write!(writer, "{doc}\n\n")?; | ||
write!(writer, "```json\n{defaults}\n```\n\n")?; | ||
|
||
if inner { | ||
Self::get_markdown_with_depth(writer, docs, field, depth + 1)?; | ||
} | ||
|
||
field.pop(); | ||
} | ||
Ok(()) | ||
} | ||
} |
Oops, something went wrong.