diff --git a/coordinator/src/app_desc.rs b/coordinator/src/app_desc.rs
index 4c97fd38a6..a45d9ee8db 100644
--- a/coordinator/src/app_desc.rs
+++ b/coordinator/src/app_desc.rs
@@ -14,24 +14,15 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
-use std::borrow::Borrow;
-use std::collections::{BTreeMap, HashMap};
-use std::{fmt, fmt::Display, fmt::Formatter};
-
-use primitives::H256;
-use serde::de::{DeserializeOwned, DeserializeSeed, Error, Unexpected};
-use serde::de::{MapAccess, Visitor};
-use serde::{Deserialize, Deserializer};
-
use self::deserialize::Hex;
-
use super::values::Value;
+pub use crate::desc_common::{Constructor, GlobalName, LocalName, Namespaced, SimpleName};
pub use engine::Engine;
pub use genesis::Genesis;
-use once_cell::sync::Lazy;
-use regex::Regex;
+use primitives::H256;
+use serde::Deserialize;
+use std::collections::HashMap;
use std::fmt::Debug;
-use std::ops::{Deref, DerefMut};
pub use tendermint::TendermintParams;
mod deserialize;
@@ -41,105 +32,11 @@ pub(self) mod params;
mod tendermint;
pub(self) mod validator;
-macro_rules! module_delim {
- () => {
- "/"
- };
-}
-macro_rules! namespace_delim {
- () => {
- "."
- };
-}
-macro_rules! first_word {
- () => {
- r"[A-Za-z][a-z0-9]*|[A-Z][A-Z0-9]*"
- };
-}
-macro_rules! trailing_word {
- () => {
- r"[a-z0-9]+|[A-Z0-9]+"
- };
-}
-macro_rules! ident {
- () => {
- concat!(first_word!(), "(-", trailing_word!(), ")*")
- };
-}
-macro_rules! simple_name {
- () => {
- concat!("^", ident!(), "$")
- };
-}
-macro_rules! local_name {
- () => {
- concat!("^", ident!(), "(", namespace_delim!(), ident!(), ")*$")
- };
-}
-macro_rules! global_name {
- () => {
- concat!("^", ident!(), module_delim!(), ident!(), "(", namespace_delim!(), ident!(), ")*$")
- };
-}
-macro_rules! impl_name {
- ($name_type:ident, $pattern:ident, $expecting:tt) => {
- #[derive(Hash, Eq, Ord, PartialOrd, PartialEq)]
- pub struct $name_type(String);
-
- impl Deref for $name_type {
- type Target = String;
-
- fn deref(&self) -> &Self::Target {
- &self.0
- }
- }
-
- impl Borrow for $name_type {
- fn borrow(&self) -> &str {
- &self.0
- }
- }
-
- impl Debug for $name_type {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- Debug::fmt(&self.0, f)
- }
- }
-
- impl Display for $name_type {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- Display::fmt(&self.0, f)
- }
- }
-
- impl<'de> Deserialize<'de> for $name_type {
- fn deserialize>(deserializer: D) -> Result {
- deserializer
- .deserialize_str(NameVisitor {
- expecting: $expecting,
- pattern: &*$pattern,
- })
- .map($name_type)
- }
- }
- };
-}
-
-pub const MODULE_DELIMITER: &str = module_delim!();
-pub const NAMESPACE_DELIMITER: &str = namespace_delim!();
-
-static SIMPLE_NAME_RE: Lazy = Lazy::new(|| Regex::new(simple_name!()).unwrap());
-static LOCAL_NAME_RE: Lazy = Lazy::new(|| Regex::new(local_name!()).unwrap());
-static GLOBAL_NAME_RE: Lazy = Lazy::new(|| Regex::new(global_name!()).unwrap());
-
#[derive(Deserialize, Debug)]
#[serde(rename_all = "kebab-case")]
pub struct AppDesc {
// keyed with Name rather than module hash to allow for multiple instances of single module
pub modules: HashMap,
- /// The ID of the default `Sandboxer` to be used when no `Sandboxer` is specified for modules.
- #[serde(default)]
- pub default_sandboxer: String,
#[serde(default)]
pub host: HostSetup,
#[serde(default)]
@@ -162,20 +59,12 @@ impl AppDesc {
#[serde(rename_all = "kebab-case")]
pub struct ModuleSetup {
pub hash: Hex,
- #[serde(default)]
- pub sandboxer: String,
- #[serde(default)]
- pub exports: Namespaced,
- #[serde(default)]
- pub imports: Namespaced,
/// List of export names expected to hold the required services.
/// Then the module will receive imports for `@tx//`s.
/// It is mainly intended for modules providing `TxSorter` service.
#[serde(default)]
pub transactions: Vec,
#[serde(default)]
- pub init_config: Value,
- #[serde(default)]
pub genesis_config: Value,
#[serde(default)]
pub tags: HashMap,
@@ -183,12 +72,6 @@ pub struct ModuleSetup {
#[derive(Deserialize, Default, Debug)]
pub struct HostSetup {
- #[serde(default)]
- pub exports: Namespaced,
- #[serde(default)]
- pub imports: Namespaced,
- #[serde(default)]
- pub init_config: Namespaced,
#[serde(default)]
pub genesis_config: Namespaced,
#[serde(default)]
@@ -197,182 +80,6 @@ pub struct HostSetup {
pub genesis: Genesis,
}
-#[derive(Debug)]
-pub struct Constructor {
- pub name: String,
- pub args: Value,
-}
-
-struct NameVisitor {
- expecting: &'static str,
- pattern: &'static Regex,
-}
-
-impl<'de> Visitor<'de> for NameVisitor {
- type Value = String;
-
- fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
- write!(formatter, "{}", self.expecting)
- }
-
- fn visit_str(self, v: &str) -> Result {
- if !self.pattern.is_match(v) {
- Err(E::invalid_value(Unexpected::Str(v), &self.expecting))
- } else {
- Ok(v.to_owned())
- }
- }
-}
-
-impl_name!(SimpleName, SIMPLE_NAME_RE, "a kebab-cased identifier");
-
-impl_name!(LocalName, LOCAL_NAME_RE, "a name consisting of identifiers separated by dots");
-
-impl_name!(GlobalName, GLOBAL_NAME_RE, "a namespaced name qualified with module name");
-
-impl GlobalName {
- pub fn module(&self) -> &str {
- let delimiter_index = self.0.find(MODULE_DELIMITER).expect("a module name followed by a module delimiter");
- &self.0[0..delimiter_index]
- }
-
- pub fn name(&self) -> &str {
- let delimiter_index = self.0.find(MODULE_DELIMITER).expect("a module name followed by a module delimiter");
- &self.0[delimiter_index + 1..]
- }
-}
-
-struct ConstructorVisitor;
-
-impl<'de> Deserialize<'de> for Constructor {
- fn deserialize>(deserializer: D) -> Result {
- impl<'de> Visitor<'de> for ConstructorVisitor {
- type Value = Constructor;
-
- fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
- formatter.write_str(
- "a map with single key value pair that serves \
- as a specification of a constructor call",
- )
- }
-
- fn visit_map>(self, mut map: M) -> Result {
- match map.next_entry()? {
- Some((name, args)) => match map.next_key::()? {
- Some(_) => Err(Error::custom("Single constructor must be specified")),
- None => Ok(Constructor {
- name,
- args,
- }),
- },
- None => Err(Error::custom("No constructor specified")),
- }
- }
- }
- deserializer.deserialize_map(ConstructorVisitor)
- }
-}
-
-pub struct Namespaced(BTreeMap);
-
-impl Debug for Namespaced {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- Debug::fmt(&self.0, f)
- }
-}
-
-impl Default for Namespaced {
- fn default() -> Self {
- Namespaced(Default::default())
- }
-}
-
-impl Deref for Namespaced {
- type Target = BTreeMap;
-
- fn deref(&self) -> &Self::Target {
- &self.0
- }
-}
-
-impl DerefMut for Namespaced {
- fn deref_mut(&mut self) -> &mut Self::Target {
- &mut self.0
- }
-}
-
-const NAMESPACE_PREFIX: char = '\\';
-
-impl From> for BTreeMap {
- fn from(from: Namespaced) -> Self {
- from.0
- }
-}
-
-struct NamespacedMapVisitor<'a, T: DeserializeOwned> {
- prefix: String,
- map: &'a mut BTreeMap,
-}
-
-impl<'a, 'de, T: DeserializeOwned> Visitor<'de> for NamespacedMapVisitor<'a, T> {
- type Value = ();
-
- fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
- formatter.write_str("a map with a given type or a nested namespace as values")
- }
-
- fn visit_map>(self, mut map: A) -> Result {
- fn to_qualified<'s>(prefix: &'s str, key: &'s str) -> String {
- if prefix.is_empty() {
- key.to_owned()
- } else {
- String::with_capacity(prefix.len() + key.len() + 1) + prefix + NAMESPACE_DELIMITER + key
- }
- }
-
- while let Some(key) = map.next_key::()? {
- if key.starts_with(NAMESPACE_PREFIX) {
- let key_part = &key[1..];
- if !LOCAL_NAME_RE.is_match(key_part) {
- return Err(A::Error::invalid_value(Unexpected::Str(&key), &"an @-prefixed qualified name"))
- }
- let prefix = to_qualified(&self.prefix, key_part);
- map.next_value_seed(NamespacedMapVisitor {
- prefix,
- map: self.map,
- })?;
- } else {
- if !LOCAL_NAME_RE.is_match(&key) {
- return Err(A::Error::invalid_value(Unexpected::Str(&key), &"a qualified name"))
- }
- let qualified_key = to_qualified(&self.prefix, &key);
- self.map.insert(qualified_key, map.next_value::()?);
- }
- }
- Ok(())
- }
-}
-
-impl<'de, T: DeserializeOwned + 'de> Deserialize<'de> for Namespaced {
- fn deserialize>(deserializer: D) -> Result {
- let mut map = BTreeMap::new();
- deserializer.deserialize_map(NamespacedMapVisitor {
- prefix: String::new(),
- map: &mut map,
- })?;
- Ok(Namespaced(map))
- }
-}
-
-impl<'a, 'de, T: DeserializeOwned> DeserializeSeed<'de> for NamespacedMapVisitor<'a, T> {
- type Value = ();
-
- fn deserialize>(self, deserializer: D) -> Result {
- deserializer.deserialize_map(self)?;
- Ok(())
- }
-}
-
#[cfg(test)]
mod tests {
use crate::app_desc::AppDesc;
@@ -386,26 +93,12 @@ mod tests {
hash = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
transactions = ["has-seq"]
-[modules.awesome-module.init-config]
-test = 1
-
[modules.awesome-module.init-config.test1]
key1 = 1
key2 = "sdfsdaf"
-[modules.awesome-module.exports]
-init-genesis.init-genesis = {}
-init-chain.init-chain = {}
-update-chain.update-chain = {}
-
[host]
-[host.imports]
-a = "awesome-module/a.a"
-
-[host.imports."\\namespace"]
-"b.b" = "asdfsdaf-asdf"
-
[transactions]
great-tx = "awesome-module"
diff --git a/coordinator/src/app_desc/params.rs b/coordinator/src/app_desc/params.rs
index 8eda1ccb41..e6ed4c70ac 100644
--- a/coordinator/src/app_desc/params.rs
+++ b/coordinator/src/app_desc/params.rs
@@ -14,45 +14,11 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
-use crate::values::Value;
-use crate::{
- app_desc::{AppDesc, HostSetup, ModuleSetup},
- values::TOMLValueDeserializer,
-};
+use crate::app_desc::{AppDesc, HostSetup, ModuleSetup};
+use crate::desc_common::params::Merger;
use anyhow::Context as _;
-use handlebars::{no_escape, Context, Handlebars, TemplateRenderError};
use std::collections::BTreeMap;
-/// You use Handlebars style template in app descriptor.
-/// some-config = "{{other-variable}}"
-/// By starting "@" you can express map or array using template.
-/// some-config-array = "@[ {{variable1}}, {{variable2}} ]"
-/// some-config-map = "@[ \"key\" = {{value-variable}} ]"
-/// If you want to start a string value please use "@@"
-/// some-config = "@@start from @"
-struct Merger<'reg> {
- registry: Handlebars<'reg>,
- context: Context,
-}
-
-#[allow(dead_code)]
-impl<'reg> Merger<'reg> {
- fn new(params: &BTreeMap) -> Merger<'reg> {
- let mut registry = Handlebars::new();
- registry.register_escape_fn(no_escape);
- registry.set_strict_mode(true);
-
- Merger {
- registry,
- context: Context::wraps(params).unwrap(),
- }
- }
-
- fn merge(&self, s: &str) -> Result {
- self.registry.render_template_with_context(s, &self.context)
- }
-}
-
impl AppDesc {
pub fn merge_params(&mut self, params: &BTreeMap) -> anyhow::Result<()> {
let mut merged_params = self.param_defaults.clone();
@@ -71,10 +37,6 @@ impl AppDesc {
impl ModuleSetup {
fn merge_params(&mut self, merger: &Merger) -> anyhow::Result<()> {
- for (export, cons) in self.exports.iter_mut() {
- cons.args.merge_params(merger).with_context(|| format!("exports > {} = {}", export, cons.name))?;
- }
- self.init_config.merge_params(merger).context("init-config")?;
self.genesis_config.merge_params(merger).context("genesis-config")?;
Ok(())
}
@@ -82,145 +44,9 @@ impl ModuleSetup {
impl HostSetup {
fn merge_params(&mut self, merger: &Merger) -> anyhow::Result<()> {
- for (export, cons) in self.exports.iter_mut() {
- cons.args.merge_params(merger).with_context(|| format!("host > exports > {} = {}", export, cons.name))?;
- }
- for (config, value) in self.init_config.iter_mut() {
- value.merge_params(merger).with_context(|| format!("host > init-config > {}", config))?;
- }
for (config, value) in self.genesis_config.iter_mut() {
value.merge_params(merger).with_context(|| format!("host > genesis-config > {}", config))?;
}
Ok(())
}
}
-
-impl Value {
- fn merge_params(&mut self, merger: &Merger) -> anyhow::Result<()> {
- match self {
- Self::String(s) => {
- if s.starts_with("@@") {
- // Change @@ to @ and type is string
- let merged = merger.merge(&s[1..])?;
- *self = Value::String(merged);
- } else if s.starts_with('@') {
- // Remove @ and type is anything
- let merged = merger.merge(&s[1..])?;
- *self = TOMLValueDeserializer::deserialize(&merged)?;
- } else {
- // Type is string
- let merged = merger.merge(s)?;
- *self = Value::String(merged);
- }
- }
- Self::List(list) => {
- for v in list {
- v.merge_params(merger)?
- }
- }
- Self::Map(map) => {
- for v in map.values_mut() {
- v.merge_params(merger)?
- }
- }
- _ => {}
- }
- Ok(())
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::Merger;
- use crate::values::Value;
-
- #[test]
- fn merge_into_string_value() {
- let params = vec![("hello".to_owned(), "world".to_owned())].into_iter().collect();
- let merger = Merger::new(¶ms);
- let mut value = Value::String("=={{hello}}==".to_owned());
- value.merge_params(&merger).unwrap();
- assert_eq!(value, Value::String("==world==".to_owned()))
- }
-
- #[test]
- fn merge_into_non_string_value() {
- let params = vec![("hello".to_owned(), "world".to_owned())].into_iter().collect();
- let merger = Merger::new(¶ms);
- let mut value = Value::Int(123);
- value.merge_params(&merger).unwrap();
- assert_eq!(value, Value::Int(123))
- }
-
- #[test]
- fn merge_into_value_in_map() {
- let params = vec![("hello".to_owned(), "world".to_owned())].into_iter().collect();
- let merger = Merger::new(¶ms);
- let mut value = Value::Map(
- vec![(String::from("1"), Value::Int(1)), (String::from("2"), Value::String(String::from("=={{hello}}==")))]
- .into_iter()
- .collect(),
- );
-
- value.merge_params(&merger).unwrap();
-
- assert_eq!(
- value,
- Value::Map(
- vec![(String::from("1"), Value::Int(1)), (String::from("2"), Value::String(String::from("==world==")))]
- .into_iter()
- .collect()
- )
- );
- }
-
- #[test]
- fn merge_into_value_in_list() {
- let params = vec![("hello".to_owned(), "world".to_owned())].into_iter().collect();
- let merger = Merger::new(¶ms);
- let mut value = Value::List(vec![Value::Int(1), Value::String(String::from("=={{hello}}=="))]);
-
- value.merge_params(&merger).unwrap();
-
- assert_eq!(value, Value::List(vec![Value::Int(1), Value::String(String::from("==world=="))]));
- }
-
- #[test]
- fn merge_at() {
- let params = vec![("hello".to_owned(), "world".to_owned())].into_iter().collect();
- let merger = Merger::new(¶ms);
- let mut value = Value::String("@\"=={{hello}}==\"".to_owned());
- value.merge_params(&merger).unwrap();
- assert_eq!(value, Value::String("==world==".to_owned()))
- }
-
- #[test]
- fn merge_at_array() {
- let params = vec![("hello".to_owned(), "world".to_owned())].into_iter().collect();
- let merger = Merger::new(¶ms);
- let mut value = Value::String("@[\"{{hello}}\", \"{{hello}}\"]".to_owned());
- value.merge_params(&merger).unwrap();
- assert_eq!(value, Value::List(vec![Value::String("world".to_owned()), Value::String("world".to_owned())]))
- }
-
- #[test]
- fn merge_at_map() {
- let params = vec![("hello".to_owned(), "world".to_owned())].into_iter().collect();
- let merger = Merger::new(¶ms);
- let mut value = Value::String("@{ \"key\" = \"{{hello}}\" }".to_owned());
- value.merge_params(&merger).unwrap();
- assert_eq!(
- value,
- Value::Map(vec![("key".to_string(), Value::String("world".to_string())),].into_iter().collect())
- )
- }
-
- #[test]
- fn merge_atat() {
- let params = vec![("hello".to_owned(), "world".to_owned())].into_iter().collect();
- let merger = Merger::new(¶ms);
- let mut value = Value::String("@@=={{hello}}==".to_owned());
- value.merge_params(&merger).unwrap();
- assert_eq!(value, Value::String("@==world==".to_owned()))
- }
-}
diff --git a/coordinator/src/app_desc/validator.rs b/coordinator/src/app_desc/validator.rs
index b895606699..571bd5658d 100644
--- a/coordinator/src/app_desc/validator.rs
+++ b/coordinator/src/app_desc/validator.rs
@@ -15,43 +15,15 @@
// along with this program. If not, see .
use super::AppDesc;
-use crate::app_desc::{GlobalName, Namespaced};
use anyhow::bail;
impl AppDesc {
pub fn validate(&self) -> anyhow::Result<()> {
- self.sandboxer_specified()?;
self.tx_owners_are_valid()?;
- self.host_imports_are_valid()?;
- self.module_imports_are_valid()?;
Ok(())
}
- fn sandboxer_specified(&self) -> anyhow::Result<()> {
- if !self.default_sandboxer.is_empty() {
- return Ok(())
- }
-
- let modules_without_sandboxer: Vec<_> = self
- .modules
- .iter()
- .filter_map(|(module, setup)| {
- if setup.sandboxer.is_empty() {
- Some(&**module as &str)
- } else {
- None
- }
- })
- .collect();
-
- if modules_without_sandboxer.is_empty() {
- return Ok(())
- }
-
- bail!("No sandboxer is specified for modules: {}", modules_without_sandboxer.join(", "))
- }
-
fn tx_owners_are_valid(&self) -> anyhow::Result<()> {
let invalid_owners: Vec<(&str, &str)> = self
.transactions
@@ -78,32 +50,4 @@ impl AppDesc {
)
}
}
-
- fn host_imports_are_valid(&self) -> anyhow::Result<()> {
- self.imports_are_valid("The host", &self.host.imports)
- }
-
- fn module_imports_are_valid(&self) -> anyhow::Result<()> {
- for (module, setup) in self.modules.iter() {
- self.imports_are_valid(&format!("A module, '{}'", module), &setup.imports)?;
- }
- Ok(())
- }
-
- fn imports_are_valid(&self, importer: &str, imports: &Namespaced) -> anyhow::Result<()> {
- for (_to, from) in imports.iter() {
- let module = from.module();
- match self.modules.get(from.module()) {
- Some(setup) => {
- let name = from.name();
- if !setup.exports.contains_key(name) {
- bail!("{} imports non-existing service '{}' from '{}'", importer, name, module)
- }
- }
- None => bail!("{} imports from non-existing module: {}", importer, module),
- }
- }
-
- Ok(())
- }
}
diff --git a/coordinator/src/desc_common.rs b/coordinator/src/desc_common.rs
new file mode 100644
index 0000000000..e532c171ba
--- /dev/null
+++ b/coordinator/src/desc_common.rs
@@ -0,0 +1,297 @@
+// Copyright 2020 Kodebox, Inc.
+// This file is part of CodeChain.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+use super::values::Value;
+use once_cell::sync::Lazy;
+use regex::Regex;
+use serde::de::{DeserializeOwned, DeserializeSeed, Error, Unexpected};
+use serde::de::{MapAccess, Visitor};
+use serde::{Deserialize, Deserializer};
+use std::borrow::Borrow;
+use std::collections::BTreeMap;
+use std::fmt::Debug;
+use std::ops::{Deref, DerefMut};
+use std::{fmt, fmt::Display, fmt::Formatter};
+
+pub(crate) mod params;
+
+macro_rules! module_delim {
+ () => {
+ "/"
+ };
+}
+macro_rules! namespace_delim {
+ () => {
+ "."
+ };
+}
+macro_rules! first_word {
+ () => {
+ r"[A-Za-z][a-z0-9]*|[A-Z][A-Z0-9]*"
+ };
+}
+macro_rules! trailing_word {
+ () => {
+ r"[a-z0-9]+|[A-Z0-9]+"
+ };
+}
+macro_rules! ident {
+ () => {
+ concat!(first_word!(), "(-", trailing_word!(), ")*")
+ };
+}
+macro_rules! simple_name {
+ () => {
+ concat!("^", ident!(), "$")
+ };
+}
+macro_rules! local_name {
+ () => {
+ concat!("^", ident!(), "(", namespace_delim!(), ident!(), ")*$")
+ };
+}
+macro_rules! global_name {
+ () => {
+ concat!("^", ident!(), module_delim!(), ident!(), "(", namespace_delim!(), ident!(), ")*$")
+ };
+}
+
+pub const MODULE_DELIMITER: &str = module_delim!();
+pub const NAMESPACE_DELIMITER: &str = namespace_delim!();
+
+pub static SIMPLE_NAME_RE: Lazy = Lazy::new(|| Regex::new(simple_name!()).unwrap());
+pub static LOCAL_NAME_RE: Lazy = Lazy::new(|| Regex::new(local_name!()).unwrap());
+pub static GLOBAL_NAME_RE: Lazy = Lazy::new(|| Regex::new(global_name!()).unwrap());
+
+macro_rules! impl_name {
+ ($name_type:ident, $pattern:ident, $expecting:tt) => {
+ #[derive(Hash, Eq, Ord, PartialOrd, PartialEq)]
+ pub struct $name_type(String);
+
+ impl Deref for $name_type {
+ type Target = String;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+ }
+
+ impl Borrow for $name_type {
+ fn borrow(&self) -> &str {
+ &self.0
+ }
+ }
+
+ impl Debug for $name_type {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ Debug::fmt(&self.0, f)
+ }
+ }
+
+ impl Display for $name_type {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ Display::fmt(&self.0, f)
+ }
+ }
+
+ impl<'de> Deserialize<'de> for $name_type {
+ fn deserialize>(deserializer: D) -> Result {
+ deserializer
+ .deserialize_str(NameVisitor {
+ expecting: $expecting,
+ pattern: &*$pattern,
+ })
+ .map($name_type)
+ }
+ }
+ };
+}
+
+impl_name!(SimpleName, SIMPLE_NAME_RE, "a kebab-cased identifier");
+
+impl_name!(LocalName, LOCAL_NAME_RE, "a name consisting of identifiers separated by dots");
+
+impl_name!(GlobalName, GLOBAL_NAME_RE, "a namespaced name qualified with module name");
+
+impl GlobalName {
+ pub fn module(&self) -> &str {
+ let delimiter_index = self.0.find(MODULE_DELIMITER).expect("a module name followed by a module delimiter");
+ &self.0[0..delimiter_index]
+ }
+
+ pub fn name(&self) -> &str {
+ let delimiter_index = self.0.find(MODULE_DELIMITER).expect("a module name followed by a module delimiter");
+ &self.0[delimiter_index + 1..]
+ }
+}
+
+struct ConstructorVisitor;
+
+impl<'de> Deserialize<'de> for Constructor {
+ fn deserialize>(deserializer: D) -> Result {
+ impl<'de> Visitor<'de> for ConstructorVisitor {
+ type Value = Constructor;
+
+ fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
+ formatter.write_str(
+ "a map with single key value pair that serves \
+ as a specification of a constructor call",
+ )
+ }
+
+ fn visit_map>(self, mut map: M) -> Result {
+ match map.next_entry()? {
+ Some((name, args)) => match map.next_key::()? {
+ Some(_) => Err(Error::custom("Single constructor must be specified")),
+ None => Ok(Constructor {
+ name,
+ args,
+ }),
+ },
+ None => Err(Error::custom("No constructor specified")),
+ }
+ }
+ }
+ deserializer.deserialize_map(ConstructorVisitor)
+ }
+}
+
+pub struct Namespaced(BTreeMap);
+
+impl Debug for Namespaced {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ Debug::fmt(&self.0, f)
+ }
+}
+
+impl Default for Namespaced {
+ fn default() -> Self {
+ Namespaced(Default::default())
+ }
+}
+
+impl Deref for Namespaced {
+ type Target = BTreeMap;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl DerefMut for Namespaced {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.0
+ }
+}
+
+const NAMESPACE_PREFIX: char = '\\';
+
+impl From> for BTreeMap {
+ fn from(from: Namespaced) -> Self {
+ from.0
+ }
+}
+
+struct NamespacedMapVisitor<'a, T: DeserializeOwned> {
+ prefix: String,
+ map: &'a mut BTreeMap,
+}
+
+impl<'a, 'de, T: DeserializeOwned> Visitor<'de> for NamespacedMapVisitor<'a, T> {
+ type Value = ();
+
+ fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ formatter.write_str("a map with a given type or a nested namespace as values")
+ }
+
+ fn visit_map>(self, mut map: A) -> Result {
+ fn to_qualified<'s>(prefix: &'s str, key: &'s str) -> String {
+ if prefix.is_empty() {
+ key.to_owned()
+ } else {
+ String::with_capacity(prefix.len() + key.len() + 1) + prefix + NAMESPACE_DELIMITER + key
+ }
+ }
+
+ while let Some(key) = map.next_key::()? {
+ if key.starts_with(NAMESPACE_PREFIX) {
+ let key_part = &key[1..];
+ if !LOCAL_NAME_RE.is_match(key_part) {
+ return Err(A::Error::invalid_value(Unexpected::Str(&key), &"an @-prefixed qualified name"))
+ }
+ let prefix = to_qualified(&self.prefix, key_part);
+ map.next_value_seed(NamespacedMapVisitor {
+ prefix,
+ map: self.map,
+ })?;
+ } else {
+ if !LOCAL_NAME_RE.is_match(&key) {
+ return Err(A::Error::invalid_value(Unexpected::Str(&key), &"a qualified name"))
+ }
+ let qualified_key = to_qualified(&self.prefix, &key);
+ self.map.insert(qualified_key, map.next_value::()?);
+ }
+ }
+ Ok(())
+ }
+}
+
+impl<'de, T: DeserializeOwned + 'de> Deserialize<'de> for Namespaced {
+ fn deserialize>(deserializer: D) -> Result {
+ let mut map = BTreeMap::new();
+ deserializer.deserialize_map(NamespacedMapVisitor {
+ prefix: String::new(),
+ map: &mut map,
+ })?;
+ Ok(Namespaced(map))
+ }
+}
+
+impl<'a, 'de, T: DeserializeOwned> DeserializeSeed<'de> for NamespacedMapVisitor<'a, T> {
+ type Value = ();
+
+ fn deserialize>(self, deserializer: D) -> Result {
+ deserializer.deserialize_map(self)?;
+ Ok(())
+ }
+}
+
+#[derive(Debug)]
+pub struct Constructor {
+ pub name: String,
+ pub args: Value,
+}
+
+struct NameVisitor {
+ expecting: &'static str,
+ pattern: &'static Regex,
+}
+
+impl<'de> Visitor<'de> for NameVisitor {
+ type Value = String;
+
+ fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
+ write!(formatter, "{}", self.expecting)
+ }
+
+ fn visit_str(self, v: &str) -> Result {
+ if !self.pattern.is_match(v) {
+ Err(E::invalid_value(Unexpected::Str(v), &self.expecting))
+ } else {
+ Ok(v.to_owned())
+ }
+ }
+}
diff --git a/coordinator/src/desc_common/params.rs b/coordinator/src/desc_common/params.rs
new file mode 100644
index 0000000000..90baedd687
--- /dev/null
+++ b/coordinator/src/desc_common/params.rs
@@ -0,0 +1,180 @@
+// Copyright 2020 Kodebox, Inc.
+// This file is part of CodeChain.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+use crate::values::TOMLValueDeserializer;
+use crate::values::Value;
+use handlebars::{no_escape, Context, Handlebars, TemplateRenderError};
+use std::collections::BTreeMap;
+
+/// You use Handlebars style template in app descriptor.
+/// some-config = "{{other-variable}}"
+/// By starting "@" you can express map or array using template.
+/// some-config-array = "@[ {{variable1}}, {{variable2}} ]"
+/// some-config-map = "@[ \"key\" = {{value-variable}} ]"
+/// If you want to start a string value please use "@@"
+/// some-config = "@@start from @"
+pub struct Merger<'reg> {
+ registry: Handlebars<'reg>,
+ context: Context,
+}
+
+#[allow(dead_code)]
+impl<'reg> Merger<'reg> {
+ pub(crate) fn new(params: &BTreeMap) -> Merger<'reg> {
+ let mut registry = Handlebars::new();
+ registry.register_escape_fn(no_escape);
+ registry.set_strict_mode(true);
+
+ Merger {
+ registry,
+ context: Context::wraps(params).unwrap(),
+ }
+ }
+
+ fn merge(&self, s: &str) -> Result {
+ self.registry.render_template_with_context(s, &self.context)
+ }
+}
+
+impl Value {
+ pub(crate) fn merge_params(&mut self, merger: &Merger) -> anyhow::Result<()> {
+ match self {
+ Self::String(s) => {
+ if s.starts_with("@@") {
+ // Change @@ to @ and type is string
+ let merged = merger.merge(&s[1..])?;
+ *self = Value::String(merged);
+ } else if s.starts_with('@') {
+ // Remove @ and type is anything
+ let merged = merger.merge(&s[1..])?;
+ *self = TOMLValueDeserializer::deserialize(&merged)?;
+ } else {
+ // Type is string
+ let merged = merger.merge(s)?;
+ *self = Value::String(merged);
+ }
+ }
+ Self::List(list) => {
+ for v in list {
+ v.merge_params(merger)?
+ }
+ }
+ Self::Map(map) => {
+ for v in map.values_mut() {
+ v.merge_params(merger)?
+ }
+ }
+ _ => {}
+ }
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::Merger;
+ use crate::values::Value;
+
+ #[test]
+ fn merge_into_string_value() {
+ let params = vec![("hello".to_owned(), "world".to_owned())].into_iter().collect();
+ let merger = Merger::new(¶ms);
+ let mut value = Value::String("=={{hello}}==".to_owned());
+ value.merge_params(&merger).unwrap();
+ assert_eq!(value, Value::String("==world==".to_owned()))
+ }
+
+ #[test]
+ fn merge_into_non_string_value() {
+ let params = vec![("hello".to_owned(), "world".to_owned())].into_iter().collect();
+ let merger = Merger::new(¶ms);
+ let mut value = Value::Int(123);
+ value.merge_params(&merger).unwrap();
+ assert_eq!(value, Value::Int(123))
+ }
+
+ #[test]
+ fn merge_into_value_in_map() {
+ let params = vec![("hello".to_owned(), "world".to_owned())].into_iter().collect();
+ let merger = Merger::new(¶ms);
+ let mut value = Value::Map(
+ vec![(String::from("1"), Value::Int(1)), (String::from("2"), Value::String(String::from("=={{hello}}==")))]
+ .into_iter()
+ .collect(),
+ );
+
+ value.merge_params(&merger).unwrap();
+
+ assert_eq!(
+ value,
+ Value::Map(
+ vec![(String::from("1"), Value::Int(1)), (String::from("2"), Value::String(String::from("==world==")))]
+ .into_iter()
+ .collect()
+ )
+ );
+ }
+
+ #[test]
+ fn merge_into_value_in_list() {
+ let params = vec![("hello".to_owned(), "world".to_owned())].into_iter().collect();
+ let merger = Merger::new(¶ms);
+ let mut value = Value::List(vec![Value::Int(1), Value::String(String::from("=={{hello}}=="))]);
+
+ value.merge_params(&merger).unwrap();
+
+ assert_eq!(value, Value::List(vec![Value::Int(1), Value::String(String::from("==world=="))]));
+ }
+
+ #[test]
+ fn merge_at() {
+ let params = vec![("hello".to_owned(), "world".to_owned())].into_iter().collect();
+ let merger = Merger::new(¶ms);
+ let mut value = Value::String("@\"=={{hello}}==\"".to_owned());
+ value.merge_params(&merger).unwrap();
+ assert_eq!(value, Value::String("==world==".to_owned()))
+ }
+
+ #[test]
+ fn merge_at_array() {
+ let params = vec![("hello".to_owned(), "world".to_owned())].into_iter().collect();
+ let merger = Merger::new(¶ms);
+ let mut value = Value::String("@[\"{{hello}}\", \"{{hello}}\"]".to_owned());
+ value.merge_params(&merger).unwrap();
+ assert_eq!(value, Value::List(vec![Value::String("world".to_owned()), Value::String("world".to_owned())]))
+ }
+
+ #[test]
+ fn merge_at_map() {
+ let params = vec![("hello".to_owned(), "world".to_owned())].into_iter().collect();
+ let merger = Merger::new(¶ms);
+ let mut value = Value::String("@{ \"key\" = \"{{hello}}\" }".to_owned());
+ value.merge_params(&merger).unwrap();
+ assert_eq!(
+ value,
+ Value::Map(vec![("key".to_string(), Value::String("world".to_string())),].into_iter().collect())
+ )
+ }
+
+ #[test]
+ fn merge_atat() {
+ let params = vec![("hello".to_owned(), "world".to_owned())].into_iter().collect();
+ let merger = Merger::new(¶ms);
+ let mut value = Value::String("@@=={{hello}}==".to_owned());
+ value.merge_params(&merger).unwrap();
+ assert_eq!(value, Value::String("@==world==".to_owned()))
+ }
+}
diff --git a/coordinator/src/lib.rs b/coordinator/src/lib.rs
index b225bc44df..e8ad205a40 100644
--- a/coordinator/src/lib.rs
+++ b/coordinator/src/lib.rs
@@ -17,10 +17,13 @@
extern crate codechain_logger as clogger;
extern crate log;
+#[macro_use]
+mod desc_common;
pub mod app_desc;
pub mod context;
pub mod engine;
mod header;
+mod link_desc;
mod linkable;
pub mod module;
pub mod test_coordinator;
@@ -33,6 +36,7 @@ pub use crate::app_desc::AppDesc;
use crate::context::StorageAccess;
use crate::engine::{BlockExecutor, ExecutionId, GraphQlHandlerProvider, Initializer, TxFilter};
pub use crate::header::Header;
+pub use crate::link_desc::LinkDesc;
use crate::module::{
HandleCrimes, HandleGraphQlRequest, InitConsensus, InitGenesis, SessionId, SortedTxs, Stateful, TxOwner, TxSorter,
UpdateConsensus,
@@ -90,11 +94,11 @@ pub struct Coordinator {
const SESSION_BITS_PER_SLOT: usize = mem::size_of::() * 8;
impl Coordinator {
- pub fn from_app_desc(app_desc: &AppDesc) -> anyhow::Result {
+ pub fn from_descs(app_desc: &AppDesc, link_desc: &LinkDesc) -> anyhow::Result {
cmodule::init_modules();
let weaver = Weaver::new();
- let (sandboxes, mut services) = weaver.weave(app_desc)?;
+ let (sandboxes, mut services) = weaver.weave(app_desc, link_desc)?;
// The order of stateful decides the assignment of substorage ids. It MUST be deterministic.
services.stateful.lock().sort_by(|a, b| a.0.cmp(&b.0));
diff --git a/coordinator/src/link_desc.rs b/coordinator/src/link_desc.rs
new file mode 100644
index 0000000000..d9fb6939f5
--- /dev/null
+++ b/coordinator/src/link_desc.rs
@@ -0,0 +1,61 @@
+// Copyright 2020 Kodebox, Inc.
+// This file is part of CodeChain.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+use super::values::Value;
+use crate::desc_common::{Constructor, GlobalName, Namespaced, SimpleName};
+use serde::Deserialize;
+use std::collections::HashMap;
+
+mod params;
+mod validator;
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "kebab-case")]
+pub struct LinkDesc {
+ /// The ID of the default `Sandboxer` to be used when no `Sandboxer` is specified for modules.
+ #[serde(default)]
+ pub default_sandboxer: String,
+ pub modules: HashMap,
+ #[serde(default)]
+ pub param_defaults: Namespaced,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "kebab-case")]
+pub struct ModuleSetup {
+ #[serde(default)]
+ pub sandboxer: String,
+ #[serde(default)]
+ pub exports: Namespaced,
+ #[serde(default)]
+ pub imports: Namespaced,
+ #[serde(default)]
+ pub init_config: Value,
+}
+
+#[allow(clippy::should_implement_trait)]
+impl LinkDesc {
+ pub fn from_str(s: &str) -> anyhow::Result {
+ let link_desc: LinkDesc = toml::from_str(s)?;
+ link_desc.validate()?;
+
+ Ok(link_desc)
+ }
+
+ pub fn get(&self, module_name: &str) -> Option<&ModuleSetup> {
+ self.modules.get(module_name)
+ }
+}
diff --git a/coordinator/src/link_desc/params.rs b/coordinator/src/link_desc/params.rs
new file mode 100644
index 0000000000..6f8c6b788b
--- /dev/null
+++ b/coordinator/src/link_desc/params.rs
@@ -0,0 +1,44 @@
+// Copyright 2020 Kodebox, Inc.
+// This file is part of CodeChain.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+use super::ModuleSetup;
+use crate::{desc_common::params::Merger, LinkDesc};
+use anyhow::Context as _;
+use std::collections::BTreeMap;
+
+impl LinkDesc {
+ pub fn merge_params(&mut self, params: &BTreeMap) -> anyhow::Result<()> {
+ let mut merged_params = self.param_defaults.clone();
+ merged_params.append(&mut params.clone());
+
+ let merger = Merger::new(&merged_params);
+
+ for (name, setup) in self.modules.iter_mut() {
+ setup.merge_params(&merger).with_context(|| format!("module: {}", name))?;
+ }
+ Ok(())
+ }
+}
+
+impl ModuleSetup {
+ fn merge_params(&mut self, merger: &Merger) -> anyhow::Result<()> {
+ for (export, cons) in self.exports.iter_mut() {
+ cons.args.merge_params(merger).with_context(|| format!("exports > {} = {}", export, cons.name))?;
+ }
+ self.init_config.merge_params(merger).context("init-config")?;
+ Ok(())
+ }
+}
diff --git a/coordinator/src/link_desc/validator.rs b/coordinator/src/link_desc/validator.rs
new file mode 100644
index 0000000000..98bcb40677
--- /dev/null
+++ b/coordinator/src/link_desc/validator.rs
@@ -0,0 +1,76 @@
+// Copyright 2020 Kodebox, Inc.
+// This file is part of CodeChain.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+use crate::desc_common::GlobalName;
+use crate::{app_desc::Namespaced, LinkDesc};
+use anyhow::bail;
+
+impl LinkDesc {
+ pub fn validate(&self) -> anyhow::Result<()> {
+ self.sandboxer_specified()?;
+ self.module_imports_are_valid()?;
+
+ Ok(())
+ }
+
+ fn sandboxer_specified(&self) -> anyhow::Result<()> {
+ if !self.default_sandboxer.is_empty() {
+ return Ok(())
+ }
+
+ let modules_without_sandboxer: Vec<_> = self
+ .modules
+ .iter()
+ .filter_map(|(module, setup)| {
+ if setup.sandboxer.is_empty() {
+ Some(&**module as &str)
+ } else {
+ None
+ }
+ })
+ .collect();
+
+ if modules_without_sandboxer.is_empty() {
+ return Ok(())
+ }
+
+ bail!("No sandboxer is specified for modules: {}", modules_without_sandboxer.join(", "))
+ }
+
+ fn module_imports_are_valid(&self) -> anyhow::Result<()> {
+ for (module, setup) in self.modules.iter() {
+ self.imports_are_valid(&format!("A module, '{}'", module), &setup.imports)?;
+ }
+ Ok(())
+ }
+
+ fn imports_are_valid(&self, importer: &str, imports: &Namespaced) -> anyhow::Result<()> {
+ for (_to, from) in imports.iter() {
+ let module = from.module();
+ match self.modules.get(from.module()) {
+ Some(setup) => {
+ let name = from.name();
+ if !setup.exports.contains_key(name) {
+ bail!("{} imports non-existing service '{}' from '{}'", importer, name, module)
+ }
+ }
+ None => bail!("{} imports from non-existing module: {}", importer, module),
+ }
+ }
+
+ Ok(())
+ }
+}
diff --git a/coordinator/src/weaver.rs b/coordinator/src/weaver.rs
index be16bac2a2..3d9f5c4a03 100644
--- a/coordinator/src/weaver.rs
+++ b/coordinator/src/weaver.rs
@@ -25,7 +25,10 @@ use anyhow::{anyhow, bail, Context};
use cmodule::link::{best_linker, Port};
use cmodule::sandbox::{sandboxer, Sandbox};
-use crate::app_desc::{AppDesc, Constructor, GlobalName, HostSetup, ModuleSetup, Namespaced, SimpleName};
+use crate::{
+ app_desc::{AppDesc, Constructor, GlobalName, ModuleSetup, Namespaced, SimpleName},
+ link_desc::{self, LinkDesc},
+};
use crate::{Occurrences, Services};
use crate::{HOST_ID, SERVICES_FOR_HOST, TX_SERVICES_FOR_HOST};
@@ -59,11 +62,16 @@ impl Weaver {
Self::default()
}
- pub(super) fn weave(mut self, app_desc: &AppDesc) -> anyhow::Result<(Vec>, Services)> {
+ pub(super) fn weave(
+ mut self,
+ app_desc: &AppDesc,
+ link_desc: &LinkDesc,
+ ) -> anyhow::Result<(Vec>, Services)> {
self.modules.reserve(app_desc.modules.len());
- self.process_host(&app_desc.host)?;
- self.process_modules(&app_desc)?;
+ let host_module = link_desc.get("host").ok_or_else(|| anyhow!("can't find host module in app descriptor"))?;
+ self.process_host(host_module)?;
+ self.process_modules(app_desc, link_desc)?;
self.tx_owners =
app_desc.transactions.iter().map(|(tx_type, module)| (tx_type.clone(), (**module).clone())).collect();
self.import_tx_services_for_modules(&app_desc.modules);
@@ -76,9 +84,9 @@ impl Weaver {
Ok((linkables, self.services.write().take().unwrap()))
}
- fn process_host(&mut self, setup: &HostSetup) -> anyhow::Result<()> {
- let (exports, init_exports) = Self::process_exports(&setup.exports);
- let imports = Self::process_imports(&setup.imports);
+ fn process_host(&mut self, link: &link_desc::ModuleSetup) -> anyhow::Result<()> {
+ let (exports, init_exports) = Self::process_exports(&link.exports);
+ let imports = Self::process_imports(&link.imports);
let imports = RefCell::new(imports);
let init_exports: Vec<(String, Vec)> = init_exports
@@ -111,12 +119,15 @@ impl Weaver {
Ok(())
}
- fn process_modules(&mut self, app_desc: &AppDesc) -> anyhow::Result<()> {
+ fn process_modules(&mut self, app_desc: &AppDesc, link_desc: &LinkDesc) -> anyhow::Result<()> {
for (name, setup) in app_desc.modules.iter() {
- let sandboxer_id = if setup.sandboxer.is_empty() {
- &app_desc.default_sandboxer
+ let link =
+ link_desc.get(name).ok_or_else(|| anyhow!("Failed to find module {} in the link descriptor", name))?;
+
+ let sandboxer_id = if link.sandboxer.is_empty() {
+ &link_desc.default_sandboxer
} else {
- &setup.sandboxer
+ &link.sandboxer
};
let sandboxer = sandboxer(sandboxer_id).ok_or_else(|| anyhow!("Sandboxer unknown: {}", sandboxer_id))?;
// FIXME: assumes that path is not used to locate a module here. Fix this later when we
@@ -126,9 +137,9 @@ impl Weaver {
} else {
format!("{:x}", &setup.hash.value)
};
- let (exports, init_exports) = Self::process_exports(&setup.exports);
- let imports = RefCell::new(Self::process_imports(&setup.imports));
- let linkable = RefCell::new(sandboxer.load(&path, &setup.init_config, &*init_exports)?);
+ let (exports, init_exports) = Self::process_exports(&link.exports);
+ let imports = RefCell::new(Self::process_imports(&link.imports));
+ let linkable = RefCell::new(sandboxer.load(&path, &link.init_config, &*init_exports)?);
self.modules.insert((*name).clone(), LinkInfo {
linkable,
diff --git a/demo/README.md b/demo/README.md
index 9491565d60..f89cfaea54 100644
--- a/demo/README.md
+++ b/demo/README.md
@@ -7,13 +7,13 @@
3. Run
```
-RUST_LOG=warn ./foundry --app-desc-path app-desc.toml --config config0.ini --db-path ./db0
+RUST_LOG=warn ./foundry --app-desc-path app-desc.toml --link-desc-path link-desc.toml --config config0.ini --db-path ./db0
-RUST_LOG=warn ./foundry --app-desc-path app-desc.toml --config config1.ini --db-path ./db1
+RUST_LOG=warn ./foundry --app-desc-path app-desc.toml --link-desc-path link-desc.toml --config config1.ini --db-path ./db1
-RUST_LOG=warn ./foundry --app-desc-path app-desc.toml --config config2.ini --db-path ./db2
+RUST_LOG=warn ./foundry --app-desc-path app-desc.toml --link-desc-path link-desc.toml --config config2.ini --db-path ./db2
-RUST_LOG=warn ./foundry --app-desc-path app-desc.toml --config config3.ini --db-path ./db3
+RUST_LOG=warn ./foundry --app-desc-path app-desc.toml --link-desc-path link-desc.toml --config config3.ini --db-path ./db3
```
for each node.
diff --git a/demo/app-desc.toml b/demo/app-desc.toml
index 9286ace3e8..d87e3ed330 100644
--- a/demo/app-desc.toml
+++ b/demo/app-desc.toml
@@ -5,16 +5,6 @@ default-sandboxer = "single-process"
[modules.module-account]
hash = "a010000000012345678901234567890123456789012345678901234567890123"
-[modules.module-account.exports]
-stateful.stateful = {}
-tx-owner.tx-owner = {}
-account-manager.account-manager = {}
-get-account-and-seq.get-account-and-seq = {}
-handle-graphql-request.handle-graphql-request = {}
-
-[modules.module-account.init-config]
-thread-pool-size = 16
-
[modules.module-account.tags]
previliged = true
@@ -22,17 +12,6 @@ previliged = true
hash = "a020000000012345678901234567890123456789012345678901234567890123"
genesis-config = ["0a6902c51384a15d1062cac3a4e62c8d0c2eb02b4de7fa0a304ce4f88ea482d0", "0473f782c3aec053c37fe2bccefa9298dcf8ae3dc2262ae540a14a580ff773e6", "2502d5e6210679a19e45f3c0f93257e7a327baaf5f403f5ca1ab2685a9e1724e", "e909f311fd115ee412edcfcde88cc507370101f7635a67b9cb45390f1ccb4b5e"]
-[modules.module-staking.exports]
-init-genesis.init-genesis = {}
-init-chain.init-chain = {}
-update-chain.update-chain = {}
-
-[modules.module-staking.imports]
-token-manager = "module-token/token-manager"
-
-[modules.module-staking.init-config]
-thread-pool-size = 16
-
[modules.module-staking.tags]
previliged = true
@@ -40,37 +19,12 @@ previliged = true
hash = "a030000000012345678901234567890123456789012345678901234567890123"
genesis-config = {}
-[modules.module-stamp.exports]
-tx-owner.tx-owner = {}
-get-account-and-seq.get-account-and-seq = {}
-init-genesis.init-genesis = {}
-
-[modules.module-stamp.imports]
-account-manager = "module-account/account-manager"
-token-manager = "module-token/token-manager"
-
-[modules.module-stamp.init-config]
-thread-pool-size = 16
-
[modules.module-stamp.tags]
previliged = true
[modules.module-token]
hash = "a040000000012345678901234567890123456789012345678901234567890123"
-[modules.module-token.exports]
-tx-owner.tx-owner = {}
-stateful.stateful = {}
-token-manager.token-manager = {}
-handle-graphql-request.handle-graphql-request = {}
-get-account-and-seq.get-account-and-seq = {}
-
-[modules.module-token.imports]
-account-manager = "module-account/account-manager"
-
-[modules.module-token.init-config]
-thread-pool-size = 16
-
[modules.module-token.tags]
previliged = true
@@ -78,24 +32,12 @@ previliged = true
hash = "a050000000012345678901234567890123456789012345678901234567890123"
transactions = ["get-account-and-seq"]
-[modules.module-sorting.exports]
-tx-sorter.tx-sorter = {}
-
-[modules.module-sorting.imports]
-account-manager = "module-account/account-manager"
-
-[modules.module-sorting.init-config]
-thread-pool-size = 16
-
[modules.module-sorting.tags]
previliged = true
[modules.module-util]
hash = "a060000000012345678901234567890123456789012345678901234567890123"
-[modules.module-util.exports]
-handle-graphql-request.handle-graphql-request = {}
-
[host]
[host.engine]
diff --git a/demo/link-desc.toml b/demo/link-desc.toml
new file mode 100644
index 0000000000..cdd58835f5
--- /dev/null
+++ b/demo/link-desc.toml
@@ -0,0 +1,65 @@
+default-sandboxer = "single-process"
+
+[modules.module-account.exports]
+stateful.stateful = {}
+tx-owner.tx-owner = {}
+account-manager.account-manager = {}
+get-account-and-seq.get-account-and-seq = {}
+handle-graphql-request.handle-graphql-request = {}
+
+[modules.module-account.init-config]
+thread-pool-size = 16
+
+[modules.module-staking.exports]
+init-genesis.init-genesis = {}
+init-chain.init-chain = {}
+update-chain.update-chain = {}
+
+[modules.module-staking.imports]
+token-manager = "module-token/token-manager"
+
+[modules.module-staking.init-config]
+thread-pool-size = 16
+
+[modules.module-stamp.exports]
+tx-owner.tx-owner = {}
+get-account-and-seq.get-account-and-seq = {}
+init-genesis.init-genesis = {}
+
+[modules.module-stamp.imports]
+account-manager = "module-account/account-manager"
+token-manager = "module-token/token-manager"
+
+[modules.module-stamp.init-config]
+thread-pool-size = 16
+
+[modules.module-token.exports]
+tx-owner.tx-owner = {}
+stateful.stateful = {}
+token-manager.token-manager = {}
+handle-graphql-request.handle-graphql-request = {}
+get-account-and-seq.get-account-and-seq = {}
+
+[modules.module-token.imports]
+account-manager = "module-account/account-manager"
+
+[modules.module-token.init-config]
+thread-pool-size = 16
+
+[modules.module-sorting.exports]
+tx-sorter.tx-sorter = {}
+
+[modules.module-sorting.imports]
+account-manager = "module-account/account-manager"
+
+[modules.module-sorting.init-config]
+thread-pool-size = 16
+
+[modules.module-util.exports]
+handle-graphql-request.handle-graphql-request = {}
+
+[modules.host]
+exports = {}
+imports = {}
+init-config = {}
+
diff --git a/foundry/config/mod.rs b/foundry/config/mod.rs
index c87b8fe4dd..1286a88a98 100644
--- a/foundry/config/mod.rs
+++ b/foundry/config/mod.rs
@@ -40,8 +40,21 @@ pub struct Config {
pub config: Option,
// operating
- #[conf(no_short, long = "app-desc-path", help = "Specify the app descriptor path.")]
- pub app_desc_path: Option,
+ #[conf(
+ no_short,
+ long = "app-desc-path",
+ help = "Specify the app descriptor path.",
+ default = "\"./app-desc.toml\".to_string()"
+ )]
+ pub app_desc_path: String,
+
+ #[conf(
+ no_short,
+ long = "link-desc-path",
+ help = "Specify the link descriptor path.",
+ default = "\"./link-desc.toml\".to_string()"
+ )]
+ pub link_desc_path: String,
#[conf(
short = "i",
diff --git a/foundry/run_node.rs b/foundry/run_node.rs
index 5ca8298f72..7ff732e805 100644
--- a/foundry/run_node.rs
+++ b/foundry/run_node.rs
@@ -32,7 +32,7 @@ use ckeystore::accounts_dir::RootDiskDirectory;
use ckeystore::KeyStore;
use clogger::{EmailAlarm, LoggerConfig};
use cnetwork::{Filters, ManagingPeerdb, NetworkConfig, NetworkControl, NetworkService, RoutingTable, SocketAddr};
-use coordinator::{AppDesc, Coordinator};
+use coordinator::{AppDesc, Coordinator, LinkDesc};
use crossbeam::unbounded;
use crossbeam_channel as crossbeam;
use csync::snapshot::Service as SnapshotService;
@@ -234,9 +234,26 @@ pub fn run_node(
let time_gap_params = config.create_time_gaps();
- let mut app_desc = AppDesc::from_str(&fs::read_to_string(config.app_desc_path.as_ref().unwrap()).unwrap()).unwrap();
- app_desc.merge_params(&module_arguments).expect("error in merge params");
- let coordinator = Arc::new(Coordinator::from_app_desc(&app_desc).unwrap());
+ let mut app_desc = {
+ let app_desc_path = &config.app_desc_path;
+ let app_desc_string = fs::read_to_string(app_desc_path)
+ .map_err(|err| format!("Foundry failed to read an app desc at {}: {}", app_desc_path, err))?;
+ AppDesc::from_str(&app_desc_string).map_err(|err| format!("Foundry failed to parse app descriptor: {}", err))?
+ };
+ app_desc
+ .merge_params(&module_arguments)
+ .map_err(|err| format!("Foundry failed to merge params you supplied into the app descriptor. {}", err))?;
+ let mut link_desc = {
+ let link_desc_path = &config.link_desc_path;
+ let link_desc_string = fs::read_to_string(link_desc_path)
+ .map_err(|err| format!("Foundry failed to read a link desc at {}: {}", link_desc_path, err))?;
+ LinkDesc::from_str(&link_desc_string)
+ .map_err(|err| format!("Foundry failed to parse link descriptor: {}", err))?
+ };
+ link_desc
+ .merge_params(&module_arguments)
+ .map_err(|err| format!("Foundry failed to merge params you supplied into the link descriptor. {}", err))?;
+ let coordinator = Arc::new(Coordinator::from_descs(&app_desc, &link_desc).unwrap());
let genesis = Genesis::new(app_desc.host.genesis, coordinator.as_ref());
diff --git a/integration-test/src/lib.rs b/integration-test/src/lib.rs
index 28e65e1eb2..21961246c3 100644
--- a/integration-test/src/lib.rs
+++ b/integration-test/src/lib.rs
@@ -40,6 +40,8 @@ pub fn run_node(port: u16) -> FoundryNode {
.env("RUST_LOG", "warn")
.arg("--app-desc-path")
.arg("../timestamp/app-desc.toml")
+ .arg("--link-desc-path")
+ .arg("../timestamp/link-desc.toml")
.arg("--config")
.arg("config.tendermint-solo.ini")
.arg("--graphql-port")
diff --git a/timestamp/app-desc.toml b/timestamp/app-desc.toml
index 65d62c1b34..824184f032 100644
--- a/timestamp/app-desc.toml
+++ b/timestamp/app-desc.toml
@@ -3,16 +3,6 @@ default-sandboxer = "single-process"
[modules.module-account]
hash = "a010000000012345678901234567890123456789012345678901234567890123"
-[modules.module-account.exports]
-stateful.stateful = {}
-tx-owner.tx-owner = {}
-account-manager.account-manager = {}
-get-account-and-seq.get-account-and-seq = {}
-handle-graphql-request.handle-graphql-request = {}
-
-[modules.module-account.init-config]
-thread-pool-size = "@{{thread-pool-size}}"
-
[modules.module-account.tags]
previliged = true
@@ -20,17 +10,6 @@ previliged = true
hash = "a020000000012345678901234567890123456789012345678901234567890123"
genesis-config = ["0a6902c51384a15d1062cac3a4e62c8d0c2eb02b4de7fa0a304ce4f88ea482d0"]
-[modules.module-staking.exports]
-init-genesis.init-genesis = {}
-init-chain.init-chain = {}
-update-chain.update-chain = {}
-
-[modules.module-staking.imports]
-token-manager = "module-token/token-manager"
-
-[modules.module-staking.init-config]
-thread-pool-size = "@{{thread-pool-size}}"
-
[modules.module-staking.tags]
previliged = true
@@ -38,34 +17,12 @@ previliged = true
hash = "a030000000012345678901234567890123456789012345678901234567890123"
genesis-config = {}
-[modules.module-stamp.exports]
-tx-owner.tx-owner = {}
-get-account-and-seq.get-account-and-seq = {}
-init-genesis.init-genesis = {}
-
-[modules.module-stamp.imports]
-account-manager = "module-account/account-manager"
-token-manager = "module-token/token-manager"
-
-[modules.module-stamp.init-config]
-thread-pool-size = "@{{thread-pool-size}}"
-
[modules.module-stamp.tags]
previliged = true
[modules.module-token]
hash = "a040000000012345678901234567890123456789012345678901234567890123"
-[modules.module-token.exports]
-tx-owner.tx-owner = {}
-stateful.stateful = {}
-token-manager.token-manager = {}
-handle-graphql-request.handle-graphql-request = {}
-get-account-and-seq.get-account-and-seq = {}
-
-[modules.module-token.imports]
-account-manager = "module-account/account-manager"
-
[modules.module-token.init-config]
thread-pool-size = "@{{thread-pool-size}}"
@@ -76,15 +33,6 @@ previliged = true
hash = "a050000000012345678901234567890123456789012345678901234567890123"
transactions = ["get-account-and-seq"]
-[modules.module-sorting.exports]
-tx-sorter.tx-sorter = {}
-
-[modules.module-sorting.imports]
-account-manager = "module-account/account-manager"
-
-[modules.module-sorting.init-config]
-thread-pool-size = "@{{thread-pool-size}}"
-
[modules.module-sorting.tags]
previliged = true
diff --git a/timestamp/link-desc.toml b/timestamp/link-desc.toml
new file mode 100644
index 0000000000..05e32db630
--- /dev/null
+++ b/timestamp/link-desc.toml
@@ -0,0 +1,64 @@
+default-sandboxer = "single-process"
+
+[modules.module-account.exports]
+stateful.stateful = {}
+tx-owner.tx-owner = {}
+account-manager.account-manager = {}
+get-account-and-seq.get-account-and-seq = {}
+handle-graphql-request.handle-graphql-request = {}
+
+[modules.module-account.init-config]
+thread-pool-size = "@{{thread-pool-size}}"
+
+[modules.module-staking.exports]
+init-genesis.init-genesis = {}
+init-chain.init-chain = {}
+update-chain.update-chain = {}
+
+[modules.module-staking.imports]
+token-manager = "module-token/token-manager"
+
+[modules.module-staking.init-config]
+thread-pool-size = "@{{thread-pool-size}}"
+
+[modules.module-stamp.exports]
+tx-owner.tx-owner = {}
+get-account-and-seq.get-account-and-seq = {}
+init-genesis.init-genesis = {}
+
+[modules.module-stamp.imports]
+account-manager = "module-account/account-manager"
+token-manager = "module-token/token-manager"
+
+[modules.module-stamp.init-config]
+thread-pool-size = "@{{thread-pool-size}}"
+
+[modules.module-token.exports]
+tx-owner.tx-owner = {}
+stateful.stateful = {}
+token-manager.token-manager = {}
+handle-graphql-request.handle-graphql-request = {}
+get-account-and-seq.get-account-and-seq = {}
+
+[modules.module-token.imports]
+account-manager = "module-account/account-manager"
+
+[modules.module-token.init-config]
+thread-pool-size = "@{{thread-pool-size}}"
+
+[modules.module-sorting.exports]
+tx-sorter.tx-sorter = {}
+
+[modules.module-sorting.imports]
+account-manager = "module-account/account-manager"
+
+[modules.module-sorting.init-config]
+thread-pool-size = "@{{thread-pool-size}}"
+
+[modules.host]
+imports = {}
+exports = {}
+init-config = {}
+
+[param-defaults]
+thread-pool-size = "16"
diff --git a/timestamp/tests/integration_test.rs b/timestamp/tests/integration_test.rs
index 17aeb584b8..80ddfd2440 100644
--- a/timestamp/tests/integration_test.rs
+++ b/timestamp/tests/integration_test.rs
@@ -23,7 +23,7 @@ mod common;
use ccrypto::blake256;
use ckey::{Ed25519KeyPair, Generator, KeyPairTrait, Random};
use common::*;
-use coordinator::module::SessionId;
+use coordinator::{module::SessionId, LinkDesc};
use coordinator::{AppDesc, Coordinator};
use rand::prelude::*;
use serde_json::Value;
@@ -103,6 +103,14 @@ fn app_desc_path() -> &'static str {
}
}
+fn link_desc_path() -> &'static str {
+ if std::path::Path::exists(std::path::Path::new("./link-desc.toml")) {
+ "./link-desc.toml"
+ } else {
+ "./timestamp/link-desc.toml"
+ }
+}
+
fn app_desc() -> AppDesc {
let app_desc = std::fs::read_to_string(app_desc_path()).unwrap();
let mut app_desc = AppDesc::from_str(&app_desc).unwrap();
@@ -115,9 +123,22 @@ fn app_desc() -> AppDesc {
app_desc
}
+// We need clippy::let_and_return because of `feature = multi-process`
+// When feature != "multi-process", code become let a = x; a
+#[allow(clippy::let_and_return)]
+fn link_desc() -> LinkDesc {
+ let link_desc = std::fs::read_to_string(link_desc_path()).unwrap();
+ let link_desc = LinkDesc::from_str(&link_desc).unwrap();
+ #[cfg(feature = "multi-process")]
+ {
+ link_desc.default_sandboxer = "multi-process".to_owned();
+ }
+ link_desc
+}
+
#[test]
fn weave() {
- let c = Coordinator::from_app_desc(&app_desc()).unwrap();
+ let c = Coordinator::from_descs(&app_desc(), &link_desc()).unwrap();
assert_eq!(c.services().stateful.lock().len(), 2);
assert_eq!(c.services().init_genesis.len(), 2);
@@ -132,7 +153,7 @@ fn weave_conccurent() {
let mut joins = Vec::new();
for _ in 0..n {
joins.push(std::thread::spawn(|| {
- let c = Coordinator::from_app_desc(&app_desc()).unwrap();
+ let c = Coordinator::from_descs(&app_desc(), &link_desc()).unwrap();
assert_eq!(c.services().stateful.lock().len(), 2);
assert_eq!(c.services().init_genesis.len(), 2);
@@ -149,7 +170,7 @@ fn weave_conccurent() {
#[test]
fn simple1() {
- let coordinator = Coordinator::from_app_desc(&app_desc()).unwrap();
+ let coordinator = Coordinator::from_descs(&app_desc(), &link_desc()).unwrap();
set_empty_session(0, &coordinator);
let services = Services::new(&coordinator);
@@ -226,13 +247,13 @@ fn run_massive_token_exchange(id: SessionId, c: &Coordinator) {
#[test]
fn multiple() {
- let coordinator = Coordinator::from_app_desc(&app_desc()).unwrap();
+ let coordinator = Coordinator::from_descs(&app_desc(), &link_desc()).unwrap();
run_massive_token_exchange(0, &coordinator);
}
#[test]
fn multiple_concurrent() {
- let coordinator = Arc::new(Coordinator::from_app_desc(&app_desc()).unwrap());
+ let coordinator = Arc::new(Coordinator::from_descs(&app_desc(), &link_desc()).unwrap());
let mut joins = Vec::new();
for i in 0..4 {
let c = Arc::clone(&coordinator);
@@ -245,7 +266,7 @@ fn multiple_concurrent() {
#[test]
fn query() {
- let coordinator = Coordinator::from_app_desc(&app_desc()).unwrap();
+ let coordinator = Coordinator::from_descs(&app_desc(), &link_desc()).unwrap();
set_empty_session(0, &coordinator);
let services = Services::new(&coordinator);
@@ -275,7 +296,7 @@ fn query() {
#[test]
fn query_tx() {
- let coordinator = Coordinator::from_app_desc(&app_desc()).unwrap();
+ let coordinator = Coordinator::from_descs(&app_desc(), &link_desc()).unwrap();
set_empty_session(0, &coordinator);
let services = Services::new(&coordinator);
@@ -293,7 +314,7 @@ fn query_tx() {
#[test]
fn query_concurrent() {
- let coordinator = Coordinator::from_app_desc(&app_desc()).unwrap();
+ let coordinator = Coordinator::from_descs(&app_desc(), &link_desc()).unwrap();
set_empty_session(0, &coordinator);
let services = Services::new(&coordinator);