From f345e8f4386325bf315a9dd46609525f6ce19b93 Mon Sep 17 00:00:00 2001 From: danda Date: Wed, 16 Nov 2022 21:42:14 -0800 Subject: [PATCH 1/8] feat(node): store state for any # of networks Addresses #163 Previously chain state was stored under a path such as: ~/.kindelia/state/{blocks,heaps} With this change, the data is stored at: ~/.kindelia/state//{blocks,heaps} This enables for example flipping back and forth between a testnet and mainnet just by changing network_id in the config, or even via cli arg. --- kindelia/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kindelia/src/main.rs b/kindelia/src/main.rs index 2d4e30b..055230a 100644 --- a/kindelia/src/main.rs +++ b/kindelia/src/main.rs @@ -238,7 +238,7 @@ pub fn run_cli() -> anyhow::Result<()> { let network_id = resolve_cfg!( env = "KINDELIA_NETWORK_ID", - prop = "node.network.network_id".to_string(), + prop = "node.network.network_id", no_default = anyhow!("Missing `network_id` parameter."), cli_val = network_id, cfg = config, From 99499bd77f81bf64c11c31e04632cfdafaf9d3b2 Mon Sep 17 00:00:00 2001 From: santi Date: Thu, 17 Nov 2022 09:46:57 -0300 Subject: [PATCH 2/8] change network id --- kindelia/default.toml | 2 +- kindelia/src/main.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/kindelia/default.toml b/kindelia/default.toml index a3a59cd..423ff45 100644 --- a/kindelia/default.toml +++ b/kindelia/default.toml @@ -4,7 +4,7 @@ dir = "~/.kindelia/state" [node.network] network_id = "0xCAFE0006" -[node.networks.0xCAFE0006] +[node.network.0xCAFE0006] initial_peers = [ "64.227.110.69", "188.166.3.140", diff --git a/kindelia/src/main.rs b/kindelia/src/main.rs index 055230a..edfb50c 100644 --- a/kindelia/src/main.rs +++ b/kindelia/src/main.rs @@ -238,7 +238,7 @@ pub fn run_cli() -> anyhow::Result<()> { let network_id = resolve_cfg!( env = "KINDELIA_NETWORK_ID", - prop = "node.network.network_id", + prop = "node.network.network_id".to_string(), no_default = anyhow!("Missing `network_id` parameter."), cli_val = network_id, cfg = config, @@ -264,7 +264,7 @@ pub fn run_cli() -> anyhow::Result<()> { let initial_peers = resolve_cfg!( env = "KINDELIA_NODE_INITIAL_PEERS", - prop = format!("node.networks.{:#02X}.initial_peers", network_id), + prop = format!("node.network.{:#02X}.initial_peers", network_id), default = vec![], cli_val = initial_peers, cfg = config, From 375f80bc6724c79a87c884f711b20ca786c3529b Mon Sep 17 00:00:00 2001 From: danda Date: Wed, 23 Nov 2022 22:06:48 -0800 Subject: [PATCH 3/8] feat(core): load genesis file by network id Addresses #243 Support for loading a different genesis block for each network. The genesis block is loaded from a file whose path is the pattern: ~/.kindelia/genesis/.kdl Changes: core: * add util::genesis_path() with associated error enum * add util::genesis_code() with associated error enum * modify hvm::test_statements*() to accept network_id and load genesis block from file instead of compiled string * modify node::new() to to accept network_id and load genesis block from file instead of compiled string cli: * add network_id to test command, required by hvm::test_statements() * fix parsing of hex values for --network_id * add clap_num dep for parsing hex values --- kindelia/Cargo.toml | 1 + kindelia/src/cli.rs | 6 ++++- kindelia/src/main.rs | 19 ++++++++++++---- kindelia_core/src/node.rs | 6 ++--- kindelia_core/src/runtime/mod.rs | 15 ++++++------ kindelia_core/src/util.rs | 39 +++++++++++++++++++++++++++++++- 6 files changed, 68 insertions(+), 18 deletions(-) diff --git a/kindelia/Cargo.toml b/kindelia/Cargo.toml index 17568fa..8ddbd90 100644 --- a/kindelia/Cargo.toml +++ b/kindelia/Cargo.toml @@ -35,6 +35,7 @@ derive_builder = "0.11.2" # CLI / configuration clap = { version = "4.0.18", features = ["derive"] } clap_complete = "4.0.3" +clap-num = "1.0.2" toml = "0.5.9" # Datastructures diff --git a/kindelia/src/cli.rs b/kindelia/src/cli.rs index bdf3c82..f3d6166 100644 --- a/kindelia/src/cli.rs +++ b/kindelia/src/cli.rs @@ -3,6 +3,7 @@ use std::path::PathBuf; use clap::{Parser, Subcommand}; use clap_complete::Shell; +use clap_num::maybe_hex; use kindelia_common::Name; use kindelia_core::api::LimitStats; @@ -120,6 +121,9 @@ pub enum CliCommand { /// Whether to consider size and mana in the execution. #[clap(long)] sudo: bool, + /// Network id / magic number. + #[clap(long, value_parser=maybe_hex::)] + network_id: Option, }, /// Checks for statements in kdl file Check { @@ -205,7 +209,7 @@ pub enum CliCommand { #[clap(long)] data_dir: Option, /// Network id / magic number. - #[clap(long)] + #[clap(long, value_parser=maybe_hex::)] network_id: Option, }, /// Generate auto-completion for a shell. diff --git a/kindelia/src/main.rs b/kindelia/src/main.rs index edfb50c..eca233d 100644 --- a/kindelia/src/main.rs +++ b/kindelia/src/main.rs @@ -125,9 +125,20 @@ pub fn run_cli() -> anyhow::Result<()> { ); match parsed.command { - CliCommand::Test { file, sudo } => { + CliCommand::Test { file, sudo, network_id } => { + let config = handle_config_file(&config_path).map_err(|e| anyhow!(e))?; + let config = Some(&config); + + let network_id = resolve_cfg!( + env = "KINDELIA_NETWORK_ID", + prop = "node.network.network_id".to_string(), + no_default = anyhow!("Missing `network_id` parameter."), + cli_val = network_id, + cfg = config, + ); + let code: String = file.read_to_string()?; - test_code(&code, sudo); + test_code(network_id, &code, sudo); Ok(()) } CliCommand::Check { file, encoded, command } => { @@ -664,8 +675,8 @@ async fn join_all( Ok(()) } -pub fn test_code(code: &str, sudo: bool) { - runtime::test_statements_from_code(code, sudo); +pub fn test_code(network_id: u32, code: &str, sudo: bool) { + runtime::test_statements_from_code(network_id, code, sudo); } fn init_socket() -> Option { diff --git a/kindelia_core/src/node.rs b/kindelia_core/src/node.rs index 0d9631f..bccf8a1 100644 --- a/kindelia_core/src/node.rs +++ b/kindelia_core/src/node.rs @@ -24,7 +24,6 @@ use crate::api::{BlockInfo, FuncInfo, NodeRequest}; use crate::bits; use crate::bits::ProtoSerialize; use crate::config::MineConfig; -use crate::constants; use crate::net::{ProtoAddr, ProtoComm}; use crate::persistence::{BlockStorage, BlockStorageError}; use crate::runtime::*; @@ -832,9 +831,8 @@ impl Node { let (query_sender, query_receiver) = mpsc::sync_channel(1); let genesis_stmts = - parser::parse_code(constants::GENESIS_CODE).expect("Genesis code parses"); - let genesis_block = - build_genesis_block(&genesis_stmts).expect("Genesis block builds"); + parser::parse_code(&genesis_code(network_id).unwrap()).expect("Genesis code parses"); + let genesis_block = build_genesis_block(&genesis_stmts).expect("Genesis block builds"); let genesis_block = genesis_block.hashed(); let genesis_hash = genesis_block.get_hash().into(); diff --git a/kindelia_core/src/runtime/mod.rs b/kindelia_core/src/runtime/mod.rs index f190c58..14ad04a 100644 --- a/kindelia_core/src/runtime/mod.rs +++ b/kindelia_core/src/runtime/mod.rs @@ -113,10 +113,9 @@ use kindelia_lang::ast::{Oper, Statement, Term}; use kindelia_lang::parser::{parse_code, parse_statements, ParseErr}; use crate::bits::ProtoSerialize; -use crate::constants; use crate::persistence::DiskSer; use crate::runtime::functions::compile_func; -use crate::util::{self, U128_SIZE}; +use crate::util::{self, genesis_code, U128_SIZE}; use crate::util::{LocMap, NameMap, U120Map, U128Map}; pub use memory::{CellTag, RawCell, Loc}; @@ -3218,7 +3217,7 @@ pub fn print_io_consts() { } // Serializes, deserializes and evaluates statements -pub fn test_statements(statements: &Vec, debug: bool) { +pub fn test_statements(network_id: u32, statements: &Vec, debug: bool) { let str_0 = ast::view_statements(statements); let statements = &Vec::proto_deserialized(&statements.proto_serialized()).unwrap(); let str_1 = ast::view_statements(statements); @@ -3229,7 +3228,7 @@ pub fn test_statements(statements: &Vec, debug: bool) { // TODO: code below does not need heaps_path at all. extract heap persistence out of Runtime. let heaps_path = dirs::home_dir().unwrap().join(".kindelia").join("state").join("heaps"); - let genesis_smts = parse_code(constants::GENESIS_CODE).expect("Genesis code parses"); + let genesis_smts = parse_code(&genesis_code(network_id).unwrap()).expect("Genesis code parses"); let mut rt = init_runtime(heaps_path, &genesis_smts); let init = Instant::now(); rt.run_statements(&statements, false, debug); @@ -3245,14 +3244,14 @@ pub fn test_statements(statements: &Vec, debug: bool) { println!("[time] {} ms", init.elapsed().as_millis()); } -pub fn test_statements_from_code(code: &str, debug: bool) { +pub fn test_statements_from_code(network_id: u32, code: &str, debug: bool) { let statments = parse_statements(code); match statments { - Ok((.., statements)) => test_statements(&statements, debug), + Ok((.., statements)) => test_statements(network_id, &statements, debug), Err(ParseErr { code, erro }) => println!("{}", erro), } } -pub fn test_statements_from_file(file: &str, debug: bool) { - test_statements_from_code(&std::fs::read_to_string(file).expect("file not found"), debug); +pub fn test_statements_from_file(network_id: u32, file: &str, debug: bool) { + test_statements_from_code(network_id, &std::fs::read_to_string(file).expect("file not found"), debug); } diff --git a/kindelia_core/src/util.rs b/kindelia_core/src/util.rs index 7239371..393198f 100644 --- a/kindelia_core/src/util.rs +++ b/kindelia_core/src/util.rs @@ -5,6 +5,7 @@ // #![allow(clippy::style)] use std::collections::HashMap; +use std::path::PathBuf; use bit_vec::BitVec; @@ -13,7 +14,6 @@ use kindelia_common::{Name, U120, U256}; use std::time::{SystemTime, SystemTimeError, UNIX_EPOCH}; use crate::runtime::Loc; -use std::path::PathBuf; use thiserror::Error; @@ -244,3 +244,40 @@ pub struct FileSystemError { pub context: String, pub source: std::io::Error, } + + +// Genesis +// ======= + +#[derive(Error, Debug)] +pub(crate) enum GenesisPathError { + #[error("Home directory not found")] + HomeDirNotFound, + #[error("File not found in {0}")] + FileNotFound(PathBuf) +} + +pub(crate) fn genesis_path(network_id: u32) -> Result { + let path = dirs::home_dir().ok_or(GenesisPathError::HomeDirNotFound)?.join(".kindelia").join("genesis").join(format!("{:#02X}.kdl", network_id)); + match path.exists() { + true => Ok(path), + false => Err(GenesisPathError::FileNotFound(path)), + } +} + +#[derive(Error, Debug)] +pub(crate) enum GenesisCodeError { + #[error(transparent)] + PathError(#[from] GenesisPathError), + + #[error("Genesis block could not be read from {path:?}.")] + ReadError { + path: PathBuf, + cause: std::io::Error, + } +} + +pub(crate) fn genesis_code(network_id: u32) -> Result { + let path = genesis_path(network_id)?; + std::fs::read_to_string(&path).map_err(|e| GenesisCodeError::ReadError{path, cause: e}) +} From f6382732bcbc5c44646adc65195b38148be6f13a Mon Sep 17 00:00:00 2001 From: danda Date: Fri, 25 Nov 2022 22:25:40 -0800 Subject: [PATCH 4/8] feat(cli): git init installs genesis file kindelia_core: * genesis.kdl file removed. (moved into kindelia/genesis/networks) * genesis Statements are passed to kindelia_core api, not network_id * added kindelia_core/genesis-tests.kdl for core test cases * updated test cases * remove empty constants.rs * remove genesis_path(). (moved into kindelia/src/genesis.rs) kindelia: * all genesis files get compiled into kindelia executable * latest (by name) genesis file gets installed by kindelia init * add kindelia/genesis/README.md * add genesis.rs and move some util fn into it * cargo add include_dir * cargo fmt fixes * parse genesis statements in 'node start' and 'test' * update test(s) --- Cargo.lock | 31 ++++ kindelia/Cargo.toml | 4 + kindelia/genesis/README.md | 18 +++ .../genesis/networks/0xCAFE0006.kdl | 0 kindelia/src/genesis.rs | 79 +++++++++ kindelia/src/main.rs | 15 +- kindelia/tests/cli.rs | 2 +- kindelia_core/benches/bench.rs | 7 +- kindelia_core/genesis-tests.kdl | 150 ++++++++++++++++++ kindelia_core/src/constants.rs | 5 - kindelia_core/src/lib.rs | 1 - kindelia_core/src/node.rs | 8 +- kindelia_core/src/runtime/mod.rs | 17 +- kindelia_core/src/test/network.rs | 6 + kindelia_core/src/test/util.rs | 5 +- 15 files changed, 322 insertions(+), 26 deletions(-) create mode 100644 kindelia/genesis/README.md rename kindelia_core/genesis.kdl => kindelia/genesis/networks/0xCAFE0006.kdl (100%) create mode 100644 kindelia/src/genesis.rs create mode 100644 kindelia_core/genesis-tests.kdl delete mode 100644 kindelia_core/src/constants.rs diff --git a/Cargo.lock b/Cargo.lock index d8f0539..accf91d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -478,6 +478,15 @@ dependencies = [ "termcolor", ] +[[package]] +name = "clap-num" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488557e97528174edaa2ee268b23a809e0c598213a4bbcb4f34575a46fda147e" +dependencies = [ + "num-traits", +] + [[package]] name = "clap_complete" version = "4.0.5" @@ -1354,6 +1363,25 @@ dependencies = [ "syn", ] +[[package]] +name = "include_dir" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18762faeff7122e89e0857b02f7ce6fcc0d101d5e9ad2ad7846cc01d61b7f19e" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b139284b5cf57ecfa712bcc66950bb635b31aff41c188e8a4cfc758eca374a3f" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "indexmap" version = "1.9.1" @@ -1450,12 +1478,14 @@ dependencies = [ "anyhow", "assert_cmd", "clap 4.0.22", + "clap-num", "clap_complete", "derive_builder", "dirs", "fastrand", "hex", "httpmock", + "include_dir", "kindelia_client", "kindelia_common", "kindelia_core", @@ -1465,6 +1495,7 @@ dependencies = [ "rstest", "serde", "serde_json", + "thiserror", "tokio", "toml", "warp", diff --git a/kindelia/Cargo.toml b/kindelia/Cargo.toml index 8ddbd90..2453c48 100644 --- a/kindelia/Cargo.toml +++ b/kindelia/Cargo.toml @@ -31,6 +31,7 @@ anyhow = { version = "1.0.66", features = ["backtrace"] } dirs = "4.0.0" hex = "0.4" derive_builder = "0.11.2" +include_dir = "0.7.3" # CLI / configuration clap = { version = "4.0.18", features = ["derive"] } @@ -50,6 +51,9 @@ warp = "0.3" tokio = { version = "1.19.1", features = ["sync"] } +# Errors +thiserror = "1.0.37" + [dev-dependencies] assert_cmd = "1.0.1" fastrand = "1.7.0" diff --git a/kindelia/genesis/README.md b/kindelia/genesis/README.md new file mode 100644 index 0000000..42e9324 --- /dev/null +++ b/kindelia/genesis/README.md @@ -0,0 +1,18 @@ +# About this directory + +The `genesis/networks` directory holds genesis block files, one per network. + +Important! All files inside the `networks` sub-directory get compiled into the +`kindelia` executable. So please do not put any other type of file inside and +observe the naming convention. + +The files are named to match hexadecimal network identifiers as specified +in the config file with extension `.kdl`. For example, `network/0xCAFE0006.kdl` corresponds to network `0xCAFE0006`. + +Typically a new network is created by bumping this value, eg creating the +file `networks/0xCAFE0007.kdl`. Also `../default.toml` should be updated to match. + +The genesis file with the highest hex value is installed into the +user's home directory when `kindelia init` is run. + + diff --git a/kindelia_core/genesis.kdl b/kindelia/genesis/networks/0xCAFE0006.kdl similarity index 100% rename from kindelia_core/genesis.kdl rename to kindelia/genesis/networks/0xCAFE0006.kdl diff --git a/kindelia/src/genesis.rs b/kindelia/src/genesis.rs new file mode 100644 index 0000000..e9b80fc --- /dev/null +++ b/kindelia/src/genesis.rs @@ -0,0 +1,79 @@ +use include_dir::{include_dir, Dir, File}; +use std::path::{Path, PathBuf}; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum GenesisPathError { + #[error("Home directory not found")] + HomeDirNotFound, + #[error("File not found in {0}")] + FileNotFound(PathBuf), +} + +pub fn genesis_path(network_id: u32) -> Result { + let path = dirs::home_dir() + .ok_or(GenesisPathError::HomeDirNotFound)? + .join(".kindelia") + .join("genesis") + .join(format!("{:#02X}.kdl", network_id)); + match path.exists() { + true => Ok(path), + false => Err(GenesisPathError::FileNotFound(path)), + } +} + +#[derive(Error, Debug)] +pub enum GenesisCodeError { + #[error(transparent)] + PathError(#[from] GenesisPathError), + + #[error("Genesis block could not be read from {path:?}.")] + ReadError { path: PathBuf, cause: std::io::Error }, +} + +pub fn genesis_code(network_id: u32) -> Result { + let path = genesis_path(network_id)?; + std::fs::read_to_string(&path) + .map_err(|e| GenesisCodeError::ReadError { path, cause: e }) +} + +#[derive(Error, Debug)] +pub enum InitGenesisError { + #[error("Could not create directory: {path:?}")] + DirNotCreated { path: std::path::PathBuf, cause: std::io::Error }, + #[error("Unable to write genesis file: {path:?}")] + FileNotWritten { path: std::path::PathBuf, cause: std::io::Error }, + #[error("Genesis file is missing from the executable")] + Missing, +} + +static GENESIS_DIR: Dir<'_> = + include_dir!("$CARGO_MANIFEST_DIR/genesis/networks"); + +/// Copies latest file from kindelia_core/genesis to /.kindelia/genesis +/// Creates target dir if not existing. +/// +/// The way this works is that all the files in kindelia_core/genesis get compiled +/// into the executable by the include_dir!() macro. With this trick we are able +/// to include files dynamically, whereas include_str!() requires a static str. +/// +/// note: we could copy over all files from kindelia_core/genesis instead. +pub fn init_genesis(dir_path: &Path) -> Result<(), InitGenesisError> { + let mut files: Vec<&File> = GENESIS_DIR.files().collect(); + + // files should be named as hex values, so we sort case insensitively + // The goal here is to find highest numeric (hex) value. + files.sort_by_cached_key(|f| f.path().as_os_str().to_ascii_uppercase()); + + let file = files.last().ok_or(InitGenesisError::Missing)?; + let fname = file.path().file_name().ok_or(InitGenesisError::Missing)?; + let fpath = dir_path.join(fname); + + let default_content = file.contents(); + std::fs::create_dir_all(dir_path).map_err(|e| { + InitGenesisError::DirNotCreated { path: dir_path.to_path_buf(), cause: e } + })?; + + std::fs::write(&fpath, default_content) + .map_err(|e| InitGenesisError::FileNotWritten { path: fpath, cause: e }) +} diff --git a/kindelia/src/main.rs b/kindelia/src/main.rs index eca233d..f8f683f 100644 --- a/kindelia/src/main.rs +++ b/kindelia/src/main.rs @@ -1,6 +1,7 @@ mod cli; mod config; mod files; +mod genesis; mod util; use anyhow::{anyhow, Context}; @@ -41,6 +42,7 @@ use util::{ }; use crate::cli::{GetStatsKind, NodeCleanBlocksCommand, NodeCleanCommand}; +use crate::genesis::{genesis_code, init_genesis}; use crate::util::init_config_file; fn main() -> anyhow::Result<()> { @@ -241,6 +243,7 @@ pub fn run_cli() -> anyhow::Result<()> { let path = default_config_path()?; eprintln!("Writing default configuration to '{}'...", path.display()); init_config_file(&path).map_err(|e| anyhow!(e))?; + init_genesis(&default_base_path()?.join("genesis"))?; Ok(()) } CliCommand::Node { command, data_dir, network_id } => { @@ -676,7 +679,11 @@ async fn join_all( } pub fn test_code(network_id: u32, code: &str, sudo: bool) { - runtime::test_statements_from_code(network_id, code, sudo); + let genesis_stmts = + parser::parse_code(&genesis_code(network_id).expect("Genesis code loads")) + .expect("Genesis code parses"); + + runtime::test_statements_from_code(&genesis_stmts, code, sudo); } fn init_socket() -> Option { @@ -845,11 +852,17 @@ pub fn start_node( // File writter let file_writter = SimpleFileStorage::new(node_config.data_path.clone())?; + let genesis_stmts = parser::parse_code( + &genesis_code(node_config.network_id).expect("Genesis code loads"), + ) + .expect("Genesis code parses"); + // Node state object let (node_query_sender, node) = Node::new( node_config.data_path, node_config.network_id, addr, + &genesis_stmts, initial_peers, comm, miner_comm, diff --git a/kindelia/tests/cli.rs b/kindelia/tests/cli.rs index 1ae201e..88c2b39 100644 --- a/kindelia/tests/cli.rs +++ b/kindelia/tests/cli.rs @@ -102,7 +102,7 @@ mod cli { #[case("../example/block_3.kdl")] #[case("../example/block_4.kdl")] #[case("../example/block_5.kdl")] - #[case("../kindelia_core/genesis.kdl")] + #[case("../kindelia_core/genesis-tests.kdl")] fn test_ser_deser(#[case] file: &str) { use kindelia_core::bits::ProtoSerialize; eprintln!("{}", file); diff --git a/kindelia_core/benches/bench.rs b/kindelia_core/benches/bench.rs index 5acd182..ae18a62 100644 --- a/kindelia_core/benches/bench.rs +++ b/kindelia_core/benches/bench.rs @@ -8,7 +8,7 @@ use kindelia_lang::parser; use primitive_types::U256; use kindelia_core::bits::ProtoSerialize; -use kindelia_core::{constants, runtime, net, node, util}; +use kindelia_core::{runtime, net, node, util}; use kindelia_core::net::ProtoComm; // KHVM @@ -25,8 +25,9 @@ pub fn temp_dir() -> PathBuf { } pub fn init_runtime(path: PathBuf) -> runtime::Runtime { + const GENESIS_CODE: &str = include_str!("../genesis-tests.kdl"); let genesis_stmts = - parser::parse_code(constants::GENESIS_CODE).expect("Genesis code parses."); + parser::parse_code(GENESIS_CODE).expect("Genesis code parses."); runtime::init_runtime(path, &genesis_stmts) } @@ -155,7 +156,7 @@ fn block_loading(c: &mut Criterion) { // create Node let (_, mut node) = - node::Node::new(dir.clone(), 0, addr, vec![], comm, None, storage, None); + node::Node::new(dir.clone(), 0, addr, &[], vec![], comm, None, storage, None); // benchmark block loading c.bench_function("block_loading", |b| b.iter(|| node.load_blocks())); diff --git a/kindelia_core/genesis-tests.kdl b/kindelia_core/genesis-tests.kdl new file mode 100644 index 0000000..4c0260d --- /dev/null +++ b/kindelia_core/genesis-tests.kdl @@ -0,0 +1,150 @@ +// T types +ctr {T0} +ctr {T1 x0} +ctr {T2 x0 x1} +ctr {T3 x0 x1 x2} +ctr {T4 x0 x1 x2 x3} +ctr {T5 x0 x1 x2 x3 x4} +ctr {T6 x0 x1 x2 x3 x4 x5} +ctr {T7 x0 x1 x2 x3 x4 x5 x6} +ctr {T8 x0 x1 x2 x3 x4 x5 x6 x7} +ctr {T9 x0 x1 x2 x3 x4 x5 x6 x7 x8} +ctr {TA x0 x1 x2 x3 x4 x5 x6 x7 x8 x9} +ctr {TB x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 x10} +ctr {TC x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 x10 x11} +ctr {TD x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 x10 x11 x12} +ctr {TE x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 x10 x11 x12 x13} +ctr {TF x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 x10 x11 x12 x13 x14} +ctr {TG x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 x10 x11 x12 x13 x14 x15} + +// An if-then-else statement +fun (If cond t f) { + (If #0 ~ f) = f + (If ~ t ~) = t +} + +// Used to pretty-print names +ctr {Name name} + +// Below, we declare the built-in IO operations + +// DONE returns from an IO operation +ctr {DONE expr} +fun (Done expr) { + (Done expr) = {DONE expr} +} + +// TAKE recovers an app's stored state +ctr {TAKE cont} +fun (Take) { + (Take) = @cont {TAKE cont} +} + +// SAVE stores the app's state +ctr {SAVE expr cont} +fun (Save expr) { + (Save expr) = @cont {SAVE expr cont} +} + +// CALL calls another IO operation, assigning +// the caller name to the current subject name +ctr {CALL name argm cont} +fun (Call name argm) { + (Call name argm) = @cont {CALL name argm cont} +} + +// SUBJ returns the name of the current subject +ctr {SUBJ cont} +fun (Subj) { + (Subj) = @cont {SUBJ cont} +} + +// FROM returns the name of the current caller +ctr {FROM cont} +fun (From) { + (From) = @cont {FROM cont} +} + +// TICK returns the current block number +ctr {TICK cont} +fun (Tick) { + (Tick) = @cont {TICK cont} +} + +// GIDX returns the block number and statement index inside the block +// of a function, register or constructor. +// Returns in the form of a U120: +// - most significant 60 bits are the block index +// - less signficiant 60 bits are the statement index +ctr {GIDX name cont} +fun (GetIdx name) { + (GetIdx name) = @cont {GIDX name cont} +} + +// STH0 returns the less significant 120 bits hash of the statement at index idx +ctr {STH0 idx cont} +fun (GetStmHash0 idx) { + (GetStmHash0 idx) = @cont {STH0 idx cont} +} + +// STH0 returns the less significant 120 bits hash of the statement at index idx +ctr {STH1 idx cont} +fun (GetStmHash1 idx) { + (GetStmHash1 idx) = @cont {STH1 idx cont} +} + +// TIME returns the current block timestamp +ctr {TIME cont} +fun (Time) { + (Time) = @cont {TIME cont} +} + +// META returns the current block metadata +ctr {META cont} +fun (Meta) { + (Meta) = @cont {META cont} +} + +// HAX0 returns the current block metadata +ctr {HAX0 cont} +fun (Hax0) { + (Hax0) = @cont {HAX0 cont} +} + +// HAX1 returns the current block metadata +ctr {HAX1 cont} +fun (Hax1) { + (Hax1) = @cont {HAX1 cont} +} + +// FAIL fails a run statement +ctr {FAIL err} +fun (Fail err) { + (Fail err) = {FAIL err} +} + +// NORM fully normalizes a term +ctr {NORM term cont} +fun (Norm term) { + (Norm term) = @cont {NORM term cont} +} + +// LOAD works like TAKE, but clones the state +fun (Load) { + (Load) = @cont {TAKE @x dup x0 x1 = x; {SAVE x0 @~ (!cont x1)}} +} + +// This is here for debugging. Will be removed. +ctr {Inc} +ctr {Get} +fun (Count action) { + (Count {Inc}) = {TAKE @x {SAVE (+ x #1) @~ {DONE #0}}} + (Count {Get}) = (!(Load) @x {DONE x}) +} with { + #0 +} + +// Registers the empty namespace. +reg { + #x7e5f4552091a69125d5dfcb7b8c265 // secret_key = 0x1 +} diff --git a/kindelia_core/src/constants.rs b/kindelia_core/src/constants.rs deleted file mode 100644 index 0807dc7..0000000 --- a/kindelia_core/src/constants.rs +++ /dev/null @@ -1,5 +0,0 @@ -// VM constants -// ============ - -/// Kdl code included on genesis block -pub const GENESIS_CODE: &str = include_str!("../genesis.kdl"); diff --git a/kindelia_core/src/lib.rs b/kindelia_core/src/lib.rs index 8dec8db..d113e2b 100644 --- a/kindelia_core/src/lib.rs +++ b/kindelia_core/src/lib.rs @@ -4,7 +4,6 @@ pub mod api; pub mod bits; pub mod config; -pub mod constants; pub mod runtime; pub mod net; pub mod node; diff --git a/kindelia_core/src/node.rs b/kindelia_core/src/node.rs index bccf8a1..0f9817f 100644 --- a/kindelia_core/src/node.rs +++ b/kindelia_core/src/node.rs @@ -820,6 +820,7 @@ impl Node { data_path: PathBuf, network_id: u32, addr: C::Address, // todo: review? https://github.com/Kindelia/Kindelia-Chain/pull/252#discussion_r1037732536 + genesis_stmts: &[Statement], initial_peers: Vec, comm: C, miner_comm: Option, @@ -830,13 +831,12 @@ impl Node { ) -> (mpsc::SyncSender>, Self) { let (query_sender, query_receiver) = mpsc::sync_channel(1); - let genesis_stmts = - parser::parse_code(&genesis_code(network_id).unwrap()).expect("Genesis code parses"); - let genesis_block = build_genesis_block(&genesis_stmts).expect("Genesis block builds"); + let genesis_block = + build_genesis_block(genesis_stmts).expect("Genesis block builds"); let genesis_block = genesis_block.hashed(); let genesis_hash = genesis_block.get_hash().into(); - let runtime = init_runtime(data_path.join("heaps"), &genesis_stmts); + let runtime = init_runtime(data_path.join("heaps"), genesis_stmts); #[rustfmt::skip] let mut node = Node { diff --git a/kindelia_core/src/runtime/mod.rs b/kindelia_core/src/runtime/mod.rs index 14ad04a..1ef5f7c 100644 --- a/kindelia_core/src/runtime/mod.rs +++ b/kindelia_core/src/runtime/mod.rs @@ -110,12 +110,12 @@ use kindelia_common::nohash_hasher::NoHashHasher; use kindelia_common::{crypto, nohash_hasher, Name, U120}; use kindelia_lang::ast; use kindelia_lang::ast::{Oper, Statement, Term}; -use kindelia_lang::parser::{parse_code, parse_statements, ParseErr}; +use kindelia_lang::parser::{parse_statements, ParseErr}; use crate::bits::ProtoSerialize; use crate::persistence::DiskSer; use crate::runtime::functions::compile_func; -use crate::util::{self, genesis_code, U128_SIZE}; +use crate::util::{self, U128_SIZE}; use crate::util::{LocMap, NameMap, U120Map, U128Map}; pub use memory::{CellTag, RawCell, Loc}; @@ -3217,7 +3217,7 @@ pub fn print_io_consts() { } // Serializes, deserializes and evaluates statements -pub fn test_statements(network_id: u32, statements: &Vec, debug: bool) { +pub fn test_statements(genesis_stmts: &[Statement], statements: &Vec, debug: bool) { let str_0 = ast::view_statements(statements); let statements = &Vec::proto_deserialized(&statements.proto_serialized()).unwrap(); let str_1 = ast::view_statements(statements); @@ -3228,8 +3228,7 @@ pub fn test_statements(network_id: u32, statements: &Vec, debug: bool // TODO: code below does not need heaps_path at all. extract heap persistence out of Runtime. let heaps_path = dirs::home_dir().unwrap().join(".kindelia").join("state").join("heaps"); - let genesis_smts = parse_code(&genesis_code(network_id).unwrap()).expect("Genesis code parses"); - let mut rt = init_runtime(heaps_path, &genesis_smts); + let mut rt = init_runtime(heaps_path, genesis_stmts); let init = Instant::now(); rt.run_statements(&statements, false, debug); println!(); @@ -3244,14 +3243,14 @@ pub fn test_statements(network_id: u32, statements: &Vec, debug: bool println!("[time] {} ms", init.elapsed().as_millis()); } -pub fn test_statements_from_code(network_id: u32, code: &str, debug: bool) { +pub fn test_statements_from_code(genesis_stmts: &[Statement], code: &str, debug: bool) { let statments = parse_statements(code); match statments { - Ok((.., statements)) => test_statements(network_id, &statements, debug), + Ok((.., statements)) => test_statements(genesis_stmts, &statements, debug), Err(ParseErr { code, erro }) => println!("{}", erro), } } -pub fn test_statements_from_file(network_id: u32, file: &str, debug: bool) { - test_statements_from_code(network_id, &std::fs::read_to_string(file).expect("file not found"), debug); +pub fn test_statements_from_file(genesis_stmts: &[Statement], file: &str, debug: bool) { + test_statements_from_code(genesis_stmts, &std::fs::read_to_string(file).expect("file not found"), debug); } diff --git a/kindelia_core/src/test/network.rs b/kindelia_core/src/test/network.rs index 1f295a8..a93035e 100644 --- a/kindelia_core/src/test/network.rs +++ b/kindelia_core/src/test/network.rs @@ -11,6 +11,7 @@ use crate::events; use crate::net::{self, ProtoComm, ProtoCommError}; use crate::node; use crate::{bits, persistence}; +use kindelia_lang::parser; use super::util::temp_dir; @@ -115,12 +116,17 @@ fn start_simulation( // Storage let storage = persistence::EmptyStorage; + let GENESIS_CODE = include_str!("../../genesis-tests.kdl"); + let genesis_stmts = + parser::parse_code(GENESIS_CODE).expect("Genesis code parses."); + // Node let node_thread = { let (node_query_sender, node) = node::Node::new( node_config.data_path, node_config.network_id, addr, + &genesis_stmts, initial_peers, comm, miner_comm, diff --git a/kindelia_core/src/test/util.rs b/kindelia_core/src/test/util.rs index 7c5567f..781d0ce 100644 --- a/kindelia_core/src/test/util.rs +++ b/kindelia_core/src/test/util.rs @@ -6,7 +6,6 @@ use std::sync::Arc; use rstest::fixture; -use crate::constants; use crate::node; use crate::runtime::debug::show_term; use crate::runtime::{ @@ -16,11 +15,13 @@ use kindelia_common::{Name, U120}; use kindelia_lang::{ast, parser}; pub fn init_runtime(path: &PathBuf) -> runtime::Runtime { + const GENESIS_CODE: &str = include_str!("../../genesis-tests.kdl"); let genesis_stmts = - parser::parse_code(constants::GENESIS_CODE).expect("Genesis code parses."); + parser::parse_code(GENESIS_CODE).expect("Genesis code parses."); runtime::init_runtime(path.clone(), &genesis_stmts) } + // =========================================================== // Aux types From ea0ec5d6345d624b3da0f4d3f16b79296367e6f9 Mon Sep 17 00:00:00 2001 From: danda Date: Sun, 4 Dec 2022 11:27:31 -0800 Subject: [PATCH 5/8] fix(cli): network_id --> genesis for test cmd This fixes some test cases that were failing when run before `kindelia init`. This failed because the tests invoke the kindelia executable and depend on statements in the genesis block. The fix is to: 1) load code from ../kindelia_core/genesis-tests.kdl by default 2) allow user to supply an alternate file with --genesis arg. cli.rs * add --genesis to test cmd main.rs * read genesis arg and load code from file if present * load code from ../kindelia_core/genesis-tests.kdl if not present * test_code() accepts genesis_code arg instead of network_id --- kindelia/src/cli.rs | 6 +++--- kindelia/src/main.rs | 32 ++++++++++++++++---------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/kindelia/src/cli.rs b/kindelia/src/cli.rs index f3d6166..592bce1 100644 --- a/kindelia/src/cli.rs +++ b/kindelia/src/cli.rs @@ -121,9 +121,9 @@ pub enum CliCommand { /// Whether to consider size and mana in the execution. #[clap(long)] sudo: bool, - /// Network id / magic number. - #[clap(long, value_parser=maybe_hex::)] - network_id: Option, + /// Path to genesis file. else use default genesis for tests. + #[clap(short, long)] + genesis: Option, }, /// Checks for statements in kdl file Check { diff --git a/kindelia/src/main.rs b/kindelia/src/main.rs index f8f683f..7e69b32 100644 --- a/kindelia/src/main.rs +++ b/kindelia/src/main.rs @@ -127,20 +127,21 @@ pub fn run_cli() -> anyhow::Result<()> { ); match parsed.command { - CliCommand::Test { file, sudo, network_id } => { - let config = handle_config_file(&config_path).map_err(|e| anyhow!(e))?; - let config = Some(&config); - - let network_id = resolve_cfg!( - env = "KINDELIA_NETWORK_ID", - prop = "node.network.network_id".to_string(), - no_default = anyhow!("Missing `network_id` parameter."), - cli_val = network_id, - cfg = config, - ); + CliCommand::Test { file, sudo, genesis } => { + let genesis_code = match genesis { + Some(p) => std::fs::read_to_string(&p).context(anyhow!( + "reading user-provided genesis prelude in {}", + p.display() + ))?, + None => { + include_str!("../../kindelia_core/genesis-tests.kdl").to_string() + } + }; + let code = file + .read_to_string() + .context(anyhow!("reading user-provided code in {}", file))?; - let code: String = file.read_to_string()?; - test_code(network_id, &code, sudo); + test_code(&genesis_code, &code, sudo); Ok(()) } CliCommand::Check { file, encoded, command } => { @@ -678,10 +679,9 @@ async fn join_all( Ok(()) } -pub fn test_code(network_id: u32, code: &str, sudo: bool) { +pub fn test_code(genesis_code: &str, code: &str, sudo: bool) { let genesis_stmts = - parser::parse_code(&genesis_code(network_id).expect("Genesis code loads")) - .expect("Genesis code parses"); + parser::parse_code(genesis_code).expect("Genesis code parses"); runtime::test_statements_from_code(&genesis_stmts, code, sudo); } From f2a8acdd8bc7689f4f35f65ac5d8494c6fcde8f7 Mon Sep 17 00:00:00 2001 From: danda Date: Sun, 4 Dec 2022 18:28:12 -0800 Subject: [PATCH 6/8] refactor(cli): simplify genesis file loading Simplifies loading of the genesis block (prelude) because we simply load the code from static string compiled into executable. Previously, we wrote the code to a file on disk during the `init` command and later read it in for other commands. genesis.rs: * remove genesis_path() and GenesisPathError * remove init_genesis() and InitGenesisError * modify genesis_code() to read from static string instead of a file main.rs: * remove call to init_genesis() README.rs: * remove note about init command installing genesis files. --- kindelia/genesis/README.md | 7 +-- kindelia/src/genesis.rs | 93 +++++++++----------------------------- kindelia/src/main.rs | 5 +- 3 files changed, 25 insertions(+), 80 deletions(-) diff --git a/kindelia/genesis/README.md b/kindelia/genesis/README.md index 42e9324..1f5f9c3 100644 --- a/kindelia/genesis/README.md +++ b/kindelia/genesis/README.md @@ -10,9 +10,4 @@ The files are named to match hexadecimal network identifiers as specified in the config file with extension `.kdl`. For example, `network/0xCAFE0006.kdl` corresponds to network `0xCAFE0006`. Typically a new network is created by bumping this value, eg creating the -file `networks/0xCAFE0007.kdl`. Also `../default.toml` should be updated to match. - -The genesis file with the highest hex value is installed into the -user's home directory when `kindelia init` is run. - - +file `networks/0xCAFE0007.kdl`. Also `../default.toml` should be updated to match. \ No newline at end of file diff --git a/kindelia/src/genesis.rs b/kindelia/src/genesis.rs index e9b80fc..3ed5404 100644 --- a/kindelia/src/genesis.rs +++ b/kindelia/src/genesis.rs @@ -1,79 +1,30 @@ -use include_dir::{include_dir, Dir, File}; -use std::path::{Path, PathBuf}; +use include_dir::{include_dir, Dir}; use thiserror::Error; -#[derive(Error, Debug)] -pub enum GenesisPathError { - #[error("Home directory not found")] - HomeDirNotFound, - #[error("File not found in {0}")] - FileNotFound(PathBuf), -} - -pub fn genesis_path(network_id: u32) -> Result { - let path = dirs::home_dir() - .ok_or(GenesisPathError::HomeDirNotFound)? - .join(".kindelia") - .join("genesis") - .join(format!("{:#02X}.kdl", network_id)); - match path.exists() { - true => Ok(path), - false => Err(GenesisPathError::FileNotFound(path)), - } -} - #[derive(Error, Debug)] pub enum GenesisCodeError { - #[error(transparent)] - PathError(#[from] GenesisPathError), - - #[error("Genesis block could not be read from {path:?}.")] - ReadError { path: PathBuf, cause: std::io::Error }, -} + #[error("Unknown network: {0}.")] + UnknownNetwork(u32), -pub fn genesis_code(network_id: u32) -> Result { - let path = genesis_path(network_id)?; - std::fs::read_to_string(&path) - .map_err(|e| GenesisCodeError::ReadError { path, cause: e }) + #[error("Invalid Utf8 in genesis block for network: {network_id:?}.")] + InvalidUtf8 { network_id: u32, source: std::str::Utf8Error }, } -#[derive(Error, Debug)] -pub enum InitGenesisError { - #[error("Could not create directory: {path:?}")] - DirNotCreated { path: std::path::PathBuf, cause: std::io::Error }, - #[error("Unable to write genesis file: {path:?}")] - FileNotWritten { path: std::path::PathBuf, cause: std::io::Error }, - #[error("Genesis file is missing from the executable")] - Missing, -} - -static GENESIS_DIR: Dir<'_> = - include_dir!("$CARGO_MANIFEST_DIR/genesis/networks"); - -/// Copies latest file from kindelia_core/genesis to /.kindelia/genesis -/// Creates target dir if not existing. -/// -/// The way this works is that all the files in kindelia_core/genesis get compiled -/// into the executable by the include_dir!() macro. With this trick we are able -/// to include files dynamically, whereas include_str!() requires a static str. -/// -/// note: we could copy over all files from kindelia_core/genesis instead. -pub fn init_genesis(dir_path: &Path) -> Result<(), InitGenesisError> { - let mut files: Vec<&File> = GENESIS_DIR.files().collect(); - - // files should be named as hex values, so we sort case insensitively - // The goal here is to find highest numeric (hex) value. - files.sort_by_cached_key(|f| f.path().as_os_str().to_ascii_uppercase()); - - let file = files.last().ok_or(InitGenesisError::Missing)?; - let fname = file.path().file_name().ok_or(InitGenesisError::Missing)?; - let fpath = dir_path.join(fname); - - let default_content = file.contents(); - std::fs::create_dir_all(dir_path).map_err(|e| { - InitGenesisError::DirNotCreated { path: dir_path.to_path_buf(), cause: e } - })?; - - std::fs::write(&fpath, default_content) - .map_err(|e| InitGenesisError::FileNotWritten { path: fpath, cause: e }) +// Todo: it would be cleaner to return the contents as &[u8] without +// UTF8 conversion. However the result ultimately gets passed to +// parse_code() as &str, so the UTF-8 conversion has to happen somewhere. +// Likely the correct thing would be to change parse_code to accept +// &[u8] instead. +pub fn genesis_code(network_id: u32) -> Result<&'static str, GenesisCodeError> { + const GENESIS_DIR: Dir<'_> = + include_dir!("$CARGO_MANIFEST_DIR/genesis/networks"); + + let fname = format!("{:#02X}.kdl", network_id); + let contents = GENESIS_DIR + .get_file(&fname) + .ok_or(GenesisCodeError::UnknownNetwork(network_id))? + .contents(); + + std::str::from_utf8(contents) + .map_err(|e| GenesisCodeError::InvalidUtf8 { network_id, source: e }) } diff --git a/kindelia/src/main.rs b/kindelia/src/main.rs index 7e69b32..f982df2 100644 --- a/kindelia/src/main.rs +++ b/kindelia/src/main.rs @@ -42,7 +42,7 @@ use util::{ }; use crate::cli::{GetStatsKind, NodeCleanBlocksCommand, NodeCleanCommand}; -use crate::genesis::{genesis_code, init_genesis}; +use crate::genesis::genesis_code; use crate::util::init_config_file; fn main() -> anyhow::Result<()> { @@ -244,7 +244,6 @@ pub fn run_cli() -> anyhow::Result<()> { let path = default_config_path()?; eprintln!("Writing default configuration to '{}'...", path.display()); init_config_file(&path).map_err(|e| anyhow!(e))?; - init_genesis(&default_base_path()?.join("genesis"))?; Ok(()) } CliCommand::Node { command, data_dir, network_id } => { @@ -853,7 +852,7 @@ pub fn start_node( let file_writter = SimpleFileStorage::new(node_config.data_path.clone())?; let genesis_stmts = parser::parse_code( - &genesis_code(node_config.network_id).expect("Genesis code loads"), + genesis_code(node_config.network_id).expect("Genesis code loads"), ) .expect("Genesis code parses"); From 8a31299b69c19fe11ed3a61c1e32728e24ccc5d6 Mon Sep 17 00:00:00 2001 From: danda Date: Tue, 6 Dec 2022 14:58:06 -0800 Subject: [PATCH 7/8] feat(cli): accept genesis statements from user Adds ability for user to specify genesis statements for a network. To avoid confusion, it is not allowed to override the genesis statements for a network_id that is built into the executable. cli.rs: * node command accepts optional genesis argument main.rs: * retrieve genesis path from: env: KINDELIA_GENESIS config: node.network..genesis cli arg: genesis * load genesis statements from file if provided and verify the network_id is not compiled into executable. --- kindelia/src/cli.rs | 3 ++ kindelia/src/main.rs | 74 ++++++++++++++++++++++++++++++++++++-------- 2 files changed, 64 insertions(+), 13 deletions(-) diff --git a/kindelia/src/cli.rs b/kindelia/src/cli.rs index 592bce1..bc7a684 100644 --- a/kindelia/src/cli.rs +++ b/kindelia/src/cli.rs @@ -211,6 +211,9 @@ pub enum CliCommand { /// Network id / magic number. #[clap(long, value_parser=maybe_hex::)] network_id: Option, + /// File containing genesis statements. + #[clap(long)] + genesis: Option, }, /// Generate auto-completion for a shell. Completion { diff --git a/kindelia/src/main.rs b/kindelia/src/main.rs index f982df2..3ceb2c4 100644 --- a/kindelia/src/main.rs +++ b/kindelia/src/main.rs @@ -7,7 +7,7 @@ mod util; use anyhow::{anyhow, Context}; use std::future::Future; use std::net::{SocketAddr, UdpSocket}; -use std::path::Path; +use std::path::{Path, PathBuf}; use clap::{CommandFactory, Parser}; use clap_complete::Shell; @@ -42,7 +42,7 @@ use util::{ }; use crate::cli::{GetStatsKind, NodeCleanBlocksCommand, NodeCleanCommand}; -use crate::genesis::genesis_code; +use crate::genesis::{genesis_code, GenesisCodeError}; use crate::util::init_config_file; fn main() -> anyhow::Result<()> { @@ -141,8 +141,7 @@ pub fn run_cli() -> anyhow::Result<()> { .read_to_string() .context(anyhow!("reading user-provided code in {}", file))?; - test_code(&genesis_code, &code, sudo); - Ok(()) + test_code(&genesis_code, &code, sudo) } CliCommand::Check { file, encoded, command } => { let code = file.read_to_string()?; @@ -246,7 +245,7 @@ pub fn run_cli() -> anyhow::Result<()> { init_config_file(&path).map_err(|e| anyhow!(e))?; Ok(()) } - CliCommand::Node { command, data_dir, network_id } => { + CliCommand::Node { command, data_dir, network_id, genesis } => { let config = handle_config_file(&config_path).map_err(|e| anyhow!(e))?; let config = Some(&config); @@ -258,6 +257,14 @@ pub fn run_cli() -> anyhow::Result<()> { cfg = config, ); + let genesis_path = resolve_cfg!( + env = "KINDELIA_GENESIS", + prop = format!("node.network.{:#02X}.genesis", network_id), + default = PathBuf::new(), + cli_val = genesis, + cfg = config, + ); + let data_path = resolve_cfg!( env = "KINDELIA_NODE_DATA_DIR", prop = "node.data.dir".to_string(), @@ -332,7 +339,13 @@ pub fn run_cli() -> anyhow::Result<()> { }; let api_config = Some(api_config); - start_node(node_cfg, api_config, node_comm, initial_peers) + start_node( + node_cfg, + api_config, + &genesis_path, + node_comm, + initial_peers, + ) } } } @@ -678,11 +691,17 @@ async fn join_all( Ok(()) } -pub fn test_code(genesis_code: &str, code: &str, sudo: bool) { - let genesis_stmts = - parser::parse_code(genesis_code).expect("Genesis code parses"); +pub fn test_code( + genesis_code: &str, + code: &str, + sudo: bool, +) -> Result<(), anyhow::Error> { + let genesis_stmts = parser::parse_code(genesis_code) + .map_err(|e| anyhow!(e)) + .context("Parsing genesis code")?; runtime::test_statements_from_code(&genesis_stmts, code, sudo); + Ok(()) } fn init_socket() -> Option { @@ -819,6 +838,7 @@ pub fn spawn_event_handlers( pub fn start_node( node_config: NodeConfig, api_config: Option, + genesis_path: &PathBuf, comm: C, initial_peers: Vec, ) -> anyhow::Result<()> { @@ -851,10 +871,7 @@ pub fn start_node( // File writter let file_writter = SimpleFileStorage::new(node_config.data_path.clone())?; - let genesis_stmts = parser::parse_code( - genesis_code(node_config.network_id).expect("Genesis code loads"), - ) - .expect("Genesis code parses"); + let genesis_stmts = genesis_statements(genesis_path, node_config.network_id)?; // Node state object let (node_query_sender, node) = Node::new( @@ -927,6 +944,37 @@ pub fn start_node( Ok(()) } +fn genesis_statements( + genesis_path: &PathBuf, + network_id: u32, +) -> Result, anyhow::Error> { + if genesis_path.as_os_str().is_empty() { + parser::parse_code( + genesis_code(network_id).context("loading genesis code")?, + ) + .map_err(|e| anyhow!(e)) + } else { + // User-provided genesis statements are not allowed for networks that + // have a genesis block compiled into the executable. + // To enforce this, we attempt to retrieve the built-in code for + // provided network_id, and if that fails with UnknownNetwork error + // then the statements are allowed. + match genesis_code(network_id) { + Ok(_) => Err(anyhow!( + "Genesis statements cannot override built-in network {:#02X}", + network_id + )), + Err(GenesisCodeError::UnknownNetwork(_n)) => { + let genesis_code = std::fs::read_to_string(genesis_path)?; + parser::parse_code(&genesis_code) + .map_err(|e| anyhow!(e)) + .context("parsing user-provided genesis code") + } + Err(e) => Err(e.into()), + } + } +} + // Shell completion // ================ From 6e2b77b79de0f6d7123c611f9fdcc4870420e5bd Mon Sep 17 00:00:00 2001 From: danda Date: Tue, 6 Dec 2022 19:20:04 -0800 Subject: [PATCH 8/8] fix(cli): use default if prop not in config file Fixes a "property X not found in config" error when the missing property has a default value. --- kindelia/src/config.rs | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/kindelia/src/config.rs b/kindelia/src/config.rs index 2042610..697f71e 100644 --- a/kindelia/src/config.rs +++ b/kindelia/src/config.rs @@ -50,7 +50,10 @@ where } if let (Some(prop_path), Some(config_values)) = (self.prop, config_values) { // If config file and argument prop path are set, read from config file - return Self::resolve_from_config_aux(config_values, &prop_path); + if let Some(v) = Self::resolve_from_config_aux(config_values, &prop_path)? + { + return Ok(v); + } } (self.default_value)() } @@ -66,10 +69,13 @@ where { if let Some(prop_path) = self.prop { if let Some(config_values) = config_values { - Self::resolve_from_config_aux(config_values, &prop_path) - } else { - (self.default_value)() + if let Some(v) = + Self::resolve_from_config_aux(config_values, &prop_path)? + { + return Ok(v); + } } + (self.default_value)() } else { Err(anyhow!( "Cannot resolve from config file config without 'prop' field set" @@ -105,18 +111,17 @@ where fn resolve_from_config_aux( config_values: &toml::Value, prop_path: &str, - ) -> anyhow::Result + ) -> anyhow::Result> where T: ArgumentFrom, { - let value = Self::get_prop(config_values, prop_path).context(anyhow!( - "Could not find prop '{}' in config file.", - prop_path - ))?; - T::arg_from(value).context(anyhow!( - "Could not convert value of '{}' into desired type", - prop_path, - )) + match Self::get_prop(config_values, prop_path) { + Some(value) => Ok(Some(T::arg_from(value).context(anyhow!( + "Could not convert value of '{}' into desired type", + prop_path, + ))?)), + None => Ok(None), + } } fn get_prop(mut value: &toml::Value, prop_path: &str) -> Option {