#![warn(
missing_debug_implementations,
missing_docs,
unsafe_code,
bare_trait_objects
)]
#![warn(clippy::pedantic, clippy::nursery)]
#![allow(
// Next `cast_*` lints don't give alternatives.
clippy::cast_possible_wrap, clippy::cast_possible_truncation, clippy::cast_sign_loss,
// Next lints produce too much noise/false positives.
clippy::module_name_repetitions, clippy::similar_names, clippy::must_use_candidate,
clippy::pub_enum_variant_names,
// '... may panic' lints.
clippy::indexing_slicing,
// Too much work to fix.
clippy::missing_errors_doc, clippy::missing_const_for_fn
)]
pub use crate::{
config_manager::DefaultConfigManager,
io::{load_config_file, save_config_file},
};
pub use exonum_rust_runtime::spec::Spec;
pub use structopt;
use exonum::{
blockchain::config::{GenesisConfig, GenesisConfigBuilder},
merkledb::RocksDB,
runtime::{RuntimeInstance, WellKnownRuntime},
};
use exonum_explorer_service::ExplorerFactory;
use exonum_node::{Node, NodeBuilder as CoreNodeBuilder};
use exonum_rust_runtime::{spec::Deploy, RustRuntimeBuilder};
use exonum_supervisor::{Supervisor, SupervisorConfig};
use exonum_system_api::SystemApiPlugin;
use structopt::StructOpt;
use tempfile::TempDir;
use std::{env, ffi::OsString, iter, path::PathBuf};
use crate::command::{Command, ExonumCommand, NodeRunConfig, StandardResult};
pub mod command;
pub mod config;
mod io;
pub mod password;
mod config_manager;
#[derive(Debug)]
pub struct NodeBuilder {
rust_runtime: RustRuntimeBuilder,
external_runtimes: Vec<RuntimeInstance>,
genesis_config: GenesisConfigBuilder,
args: Option<Vec<OsString>>,
temp_dir: Option<TempDir>,
}
impl Default for NodeBuilder {
fn default() -> Self {
Self::new()
}
}
impl NodeBuilder {
pub fn new() -> Self {
Self {
genesis_config: GenesisConfigBuilder::default(),
rust_runtime: RustRuntimeBuilder::new(),
external_runtimes: vec![],
args: None,
temp_dir: None,
}
}
#[doc(hidden)] pub fn with_args<I>(args: I) -> Self
where
I: IntoIterator,
I::Item: Into<OsString>,
{
let mut this = Self::new();
let executable = env::current_exe().map_or_else(|_| "node".into(), PathBuf::into_os_string);
let all_args = iter::once(executable)
.chain(args.into_iter().map(Into::into))
.collect();
this.args = Some(all_args);
this
}
pub fn development_node() -> anyhow::Result<Self> {
let temp_dir = TempDir::new()?;
let mut this = Self::with_args(vec![
OsString::from("run-dev"),
OsString::from("--blockchain-path"),
temp_dir.path().into(),
]);
this.temp_dir = Some(temp_dir);
Ok(this)
}
pub fn with(mut self, spec: impl Deploy) -> Self {
spec.deploy(&mut self.genesis_config, &mut self.rust_runtime);
self
}
pub fn with_external_runtime(mut self, runtime: impl WellKnownRuntime) -> Self {
self.external_runtimes.push(runtime.into());
self
}
#[doc(hidden)] pub fn execute_command(mut self) -> anyhow::Result<Option<Node>> {
let command = if let Some(args) = self.args {
Command::from_iter(args)
} else {
Command::from_args()
};
if let StandardResult::Run(run_config) = command.execute()? {
let supervisor = Self::supervisor_service(&run_config);
supervisor.deploy(&mut self.genesis_config, &mut self.rust_runtime);
Spec::new(ExplorerFactory)
.with_default_instance()
.deploy(&mut self.genesis_config, &mut self.rust_runtime);
let genesis_config = Self::genesis_config(&run_config, self.genesis_config);
let db_options = &run_config.node_config.private_config.database;
let database = RocksDB::open(run_config.db_path, db_options)?;
let node_config_path = run_config.node_config_path.to_string_lossy();
let config_manager = DefaultConfigManager::new(node_config_path.into_owned());
let rust_runtime = self.rust_runtime;
let node_config = run_config.node_config.into();
let node_keys = run_config.node_keys;
let mut node_builder = CoreNodeBuilder::new(database, node_config, node_keys)
.with_genesis_config(genesis_config)
.with_config_manager(config_manager)
.with_plugin(SystemApiPlugin)
.with_runtime_fn(|channel| rust_runtime.build(channel.endpoints_sender()));
for runtime in self.external_runtimes {
node_builder = node_builder.with_runtime(runtime);
}
Ok(Some(node_builder.build()))
} else {
Ok(None)
}
}
pub async fn run(mut self) -> anyhow::Result<()> {
let _temp_dir = self.temp_dir.take();
if let Some(node) = self.execute_command()? {
node.run().await
} else {
Ok(())
}
}
fn genesis_config(run_config: &NodeRunConfig, builder: GenesisConfigBuilder) -> GenesisConfig {
let mut config = builder.build();
config.consensus_config = run_config.node_config.public_config.consensus.clone();
config
}
fn supervisor_service(run_config: &NodeRunConfig) -> impl Deploy {
let mode = run_config
.node_config
.public_config
.general
.supervisor_mode
.clone();
Supervisor::builtin_instance(SupervisorConfig::new(mode))
}
}