diff --git a/.changelog/unreleased/features/2088-new-genesis.md b/.changelog/unreleased/features/2088-new-genesis.md new file mode 100644 index 0000000000..eacd1bf44a --- /dev/null +++ b/.changelog/unreleased/features/2088-new-genesis.md @@ -0,0 +1,11 @@ +- Added bech32m string encoding for `common::PublicKey` and `DkgPublicKey`. + ([\#2088](https://github.com/anoma/namada/pull/2088)) +- Added `--pre-genesis` argument to the wallet commands to allow to generate + keys, implicit addresses and shielded keys without having a chain setup. If + no chain is setup yet (i.e. there's no base-dir or it's empty), the wallet + defaults to use the pre-genesis wallet even without the `--pre-genesis` + flag. The pre-genesis wallet is located inside base-dir in + `pre-genesis/wallet.toml`. + ([\#2088](https://github.com/anoma/namada/pull/2088)) +- Reworked the genesis templates, setup and related utils commands. + ([\#2088](https://github.com/anoma/namada/pull/2088)) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 68943aa7ca..fe80c97694 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -582,7 +582,7 @@ jobs: /tmp/.*/e2e-test.*/setup/validator-*/.namada/logs/*.log /tmp/.*/logs/* /tmp/.*/setup/validator-* - /tmp/.*/setup/validator-*/* + /tmp/.*/setup/validator-*/* retention-days: 5 - name: Print sccache stats if: always() diff --git a/Cargo.lock b/Cargo.lock index f439e19e62..baff6188a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -278,12 +278,6 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" -[[package]] -name = "ascii" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" - [[package]] name = "assert_cmd" version = "1.0.8" @@ -1052,12 +1046,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "chunked_transfer" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cca491388666e04d7248af3f60f0c40cfb0991c72205595d7c396e3510207d1a" - [[package]] name = "ciborium" version = "0.2.1" @@ -1305,6 +1293,15 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "copy_dir" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "543d1dd138ef086e2ff05e3a48cf9da045da2033d16f8538fd76b86cd49b2ca3" +dependencies = [ + "walkdir", +] + [[package]] name = "core-foundation" version = "0.9.3" @@ -2450,17 +2447,6 @@ dependencies = [ "subtle 2.4.1", ] -[[package]] -name = "file-serve" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "547ebf393d987692a02b5d2be1c0b398b16a5b185c23a047c1d3fc3050d6d803" -dependencies = [ - "log", - "mime_guess", - "tiny_http", -] - [[package]] name = "filetime" version = "0.2.21" @@ -4049,6 +4035,7 @@ dependencies = [ "num_cpus", "once_cell", "orion", + "pretty_assertions", "proptest", "prost", "prost-types", @@ -4234,6 +4221,7 @@ version = "0.24.1" dependencies = [ "assert_matches", "async-trait", + "base58 0.2.0", "bimap", "borsh 1.0.0-alpha.4", "borsh-ext", @@ -4297,12 +4285,12 @@ dependencies = [ "clap", "color-eyre", "concat-idents", + "copy_dir", "data-encoding", "derivative", "escargot", "expectrl", "eyre", - "file-serve", "fs_extra", "hyper", "itertools 0.10.5", @@ -6827,18 +6815,6 @@ dependencies = [ "crunchy", ] -[[package]] -name = "tiny_http" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82" -dependencies = [ - "ascii", - "chunked_transfer", - "httpdate", - "log", -] - [[package]] name = "tinytemplate" version = "1.2.1" diff --git a/Cargo.toml b/Cargo.toml index 9d1885001e..7e1f114225 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,7 +79,6 @@ eyre = "0.6.5" fd-lock = "3.0.12" ferveo = {git = "https://github.com/anoma/ferveo", rev = "e5abd0acc938da90140351a65a26472eb495ce4d"} ferveo-common = {git = "https://github.com/anoma/ferveo", rev = "e5abd0acc938da90140351a65a26472eb495ce4d"} -file-serve = "0.2.0" flate2 = "1.0.22" fs_extra = "1.2.0" futures = "0.3" diff --git a/Makefile b/Makefile index dc67c3c431..1fd4e64edc 100644 --- a/Makefile +++ b/Makefile @@ -54,16 +54,16 @@ build-test: $(cargo) +$(nightly) build --tests $(jobs) build-release: - NAMADA_DEV=false $(cargo) build $(jobs) --release --timings --package namada_apps --manifest-path Cargo.toml + $(cargo) build $(jobs) --release --timings --package namada_apps --manifest-path Cargo.toml build-debug: - NAMADA_DEV=false $(cargo) build --package namada_apps --manifest-path Cargo.toml + $(cargo) build --package namada_apps --manifest-path Cargo.toml install-release: - NAMADA_DEV=false $(cargo) install --path ./apps --locked + $(cargo) install --path ./apps --locked check-release: - NAMADA_DEV=false $(cargo) check --release --package namada_apps + $(cargo) check --release --package namada_apps package: build-release scripts/make-package.sh @@ -88,7 +88,7 @@ check-crates: clippy-wasm = $(cargo) +$(nightly) clippy --manifest-path $(wasm)/Cargo.toml --all-targets -- -D warnings clippy: - NAMADA_DEV=false $(cargo) +$(nightly) clippy $(jobs) --all-targets -- -D warnings && \ + $(cargo) +$(nightly) clippy $(jobs) --all-targets -- -D warnings && \ make -C $(wasms) clippy && \ make -C $(wasms_for_tests) clippy && \ $(foreach wasm,$(wasm_templates),$(clippy-wasm) && ) true @@ -103,7 +103,7 @@ tendermint: ./scripts/get_tendermint.sh install: cometbft - NAMADA_DEV=false $(cargo) install --path ./apps --locked + $(cargo) install --path ./apps --locked cometbft: ./scripts/get_cometbft.sh @@ -126,14 +126,18 @@ audit: test: test-unit test-e2e test-wasm test-benches test-coverage: - # Run integration tests with pre-built MASP proofs - NAMADA_MASP_TEST_SEED=$(NAMADA_MASP_TEST_SEED) \ - NAMADA_MASP_TEST_PROOFS=load \ + # Run integration tests separately because they require `integration` + # feature (and without coverage) and run them with pre-built MASP proofs $(cargo) +$(nightly) llvm-cov --output-dir target \ --features namada/testing \ --html \ - -- --skip e2e --skip pos_state_machine_test \ - -Z unstable-options --report-time + -- --skip e2e --skip pos_state_machine_test --skip integration \ + -Z unstable-options --report-time && \ + NAMADA_MASP_TEST_SEED=$(NAMADA_MASP_TEST_SEED) \ + NAMADA_MASP_TEST_PROOFS=load \ + $(cargo) +$(nightly) test integration:: \ + --features integration \ + -- -Z unstable-options --report-time # NOTE: `TEST_FILTER` is prepended with `e2e::`. Since filters in `cargo test` # work with a substring search, TEST_FILTER only works if it contains a string @@ -167,7 +171,7 @@ test-integration-save-proofs: # Run integration tests without specifiying any pre-built MASP proofs option test-integration-slow: RUST_BACKTRACE=$(RUST_BACKTRACE) \ - $(cargo) +$(nightly) test integration::$(TEST_FILTER) \ + $(cargo) +$(nightly) test integration::$(TEST_FILTER) --features integration \ -Z unstable-options \ -- \ -Z unstable-options --report-time @@ -176,7 +180,7 @@ test-unit: $(cargo) +$(nightly) test \ $(TEST_FILTER) \ $(jobs) \ - -- --skip e2e --skip integration \ + -- --skip e2e --skip integration --skip pos_state_machine_test \ -Z unstable-options --report-time test-unit-mainnet: @@ -191,7 +195,7 @@ test-unit-debug: $(debug-cargo) +$(nightly) test \ $(jobs) \ $(TEST_FILTER) \ - -- --skip e2e --skip integration \ + -- --skip e2e --skip integration --skip pos_state_machine_test \ --nocapture \ -Z unstable-options --report-time diff --git a/README.md b/README.md index 18d481dc5f..ce85949f3e 100644 --- a/README.md +++ b/README.md @@ -47,10 +47,6 @@ Guide. ```shell # Build the provided validity predicate and transaction wasm modules make build-wasm-scripts-docker - -# Development (debug) build Namada, which includes a validator and some default -# accounts, whose keys and addresses are available in the wallet -NAMADA_DEV=true make ``` ### Before submitting a PR, pls make sure to run the following diff --git a/apps/Cargo.toml b/apps/Cargo.toml index ea94db19ac..8a783d4dc5 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -54,11 +54,11 @@ default = ["std", "abciplus"] mainnet = [ "namada/mainnet", ] -dev = ["namada/dev"] std = ["ed25519-consensus/std", "rand/std", "rand_core/std", "namada/std", "namada_sdk/std"] # for integration tests and test utilies -testing = ["dev", "namada_test_utils"] - +testing = ["namada_test_utils"] +benches = ["testing", "namada_test_utils"] +integration = [] abciplus = [ "namada/abciplus", "namada/tendermint-rpc", @@ -111,6 +111,7 @@ num-rational.workspace = true num-traits.workspace = true once_cell.workspace = true orion.workspace = true +pretty_assertions.workspace = true prost-types.workspace = true prost.workspace = true rand_core.workspace = true diff --git a/apps/build.rs b/apps/build.rs index 60735e7e4a..6ffb58476f 100644 --- a/apps/build.rs +++ b/apps/build.rs @@ -53,13 +53,4 @@ fn main() { // Tell Cargo that if the given file changes, to rerun this build script. println!("cargo:rerun-if-changed={}", PROTO_SRC); - - // Tell Cargo to build when the `NAMADA_DEV` env var changes - println!("cargo:rerun-if-env-changed=NAMADA_DEV"); - // Enable "dev" feature if `NAMADA_DEV` is trueish - if let Ok(dev) = env::var("NAMADA_DEV") { - if dev.to_ascii_lowercase().trim() == "true" { - println!("cargo:rustc-cfg=feature=\"dev\""); - } - } } diff --git a/apps/src/bin/namada-client/cli.rs b/apps/src/bin/namada-client/cli.rs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/apps/src/bin/namada-node/cli.rs b/apps/src/bin/namada-node/cli.rs index 6499a34e9e..7ee24251be 100644 --- a/apps/src/bin/namada-node/cli.rs +++ b/apps/src/bin/namada-node/cli.rs @@ -10,26 +10,31 @@ pub fn main() -> Result<()> { match cmd { cmds::NamadaNode::Ledger(sub) => match sub { cmds::Ledger::Run(cmds::LedgerRun(args)) => { - let wasm_dir = ctx.wasm_dir(); + let chain_ctx = ctx.take_chain_or_exit(); + let wasm_dir = chain_ctx.wasm_dir(); sleep_until(args.start_time); - ledger::run(ctx.config.ledger, wasm_dir); + ledger::run(chain_ctx.config.ledger, wasm_dir); } cmds::Ledger::RunUntil(cmds::LedgerRunUntil(args)) => { - let wasm_dir = ctx.wasm_dir(); + let mut chain_ctx = ctx.take_chain_or_exit(); + let wasm_dir = chain_ctx.wasm_dir(); sleep_until(args.time); - ctx.config.ledger.shell.action_at_height = + chain_ctx.config.ledger.shell.action_at_height = Some(args.action_at_height); - ledger::run(ctx.config.ledger, wasm_dir); + ledger::run(chain_ctx.config.ledger, wasm_dir); } cmds::Ledger::Reset(_) => { - ledger::reset(ctx.config.ledger) + let chain_ctx = ctx.take_chain_or_exit(); + ledger::reset(chain_ctx.config.ledger) .wrap_err("Failed to reset Namada node")?; } cmds::Ledger::DumpDb(cmds::LedgerDumpDb(args)) => { - ledger::dump_db(ctx.config.ledger, args); + let chain_ctx = ctx.take_chain_or_exit(); + ledger::dump_db(chain_ctx.config.ledger, args); } cmds::Ledger::RollBack(_) => { - ledger::rollback(ctx.config.ledger) + let chain_ctx = ctx.take_chain_or_exit(); + ledger::rollback(chain_ctx.config.ledger) .wrap_err("Failed to rollback the Namada node")?; } }, @@ -39,18 +44,18 @@ pub fn main() -> Result<()> { // In here, we just need to overwrite the default chain ID, in // case it's been already set to a different value if let Some(chain_id) = ctx.global_args.chain_id.as_ref() { - ctx.global_config.default_chain_id = chain_id.clone(); + ctx.global_config.default_chain_id = Some(chain_id.clone()); ctx.global_config .write(&ctx.global_args.base_dir) .unwrap_or_else(|err| { - eprintln!("Error writing global config: {}", err); + eprintln!("Error writing global config: {err}"); cli::safe_exit(1) }); + tracing::debug!( + "Generated config and set default chain ID to \ + {chain_id}" + ); } - tracing::debug!( - "Generated config and set default chain ID to {}", - &ctx.global_config.default_chain_id - ); } }, } diff --git a/apps/src/bin/namada/cli.rs b/apps/src/bin/namada/cli.rs index 0259ba525a..c7694117aa 100644 --- a/apps/src/bin/namada/cli.rs +++ b/apps/src/bin/namada/cli.rs @@ -63,7 +63,7 @@ fn handle_command(cmd: cli::cmds::Namada, raw_sub_cmd: String) -> Result<()> { fn handle_subcommand(program: &str, mut sub_args: Vec) -> Result<()> { let env_vars = env::vars_os(); - let cmd_name = if cfg!(feature = "dev") && env::var("CARGO").is_ok() { + let cmd_name = if env::var("CARGO").is_ok() { // When the command is ran from inside `cargo run`, we also want to // call the sub-command via `cargo run` to rebuild if necessary. // We do this by prepending the arguments with `cargo run` arguments. diff --git a/apps/src/lib/bench_utils.rs b/apps/src/lib/bench_utils.rs index db14551a76..4f12c5b6b1 100644 --- a/apps/src/lib/bench_utils.rs +++ b/apps/src/lib/bench_utils.rs @@ -86,6 +86,7 @@ use tempfile::TempDir; use crate::cli::context::FromContext; use crate::cli::Context; +use crate::config::global::GlobalConfig; use crate::config::TendermintMode; use crate::facade::tendermint_proto::abci::RequestInitChain; use crate::facade::tendermint_proto::google::protobuf::Timestamp; @@ -167,7 +168,6 @@ impl Default for BenchShell { None, 50 * 1024 * 1024, // 50 kiB 50 * 1024 * 1024, // 50 kiB - address::nam(), ); shell @@ -210,7 +210,8 @@ impl Default for BenchShell { // Initialize governance proposal let content_section = Section::ExtraData(Code::new(vec![])); - let voting_start_epoch = Epoch(25); + let voting_start_epoch = + Epoch(2 + params.pipeline_len + params.unbonding_len); let signed_tx = generate_tx( TX_INIT_PROPOSAL_WASM, InitProposalData { @@ -219,8 +220,8 @@ impl Default for BenchShell { author: defaults::albert_address(), r#type: ProposalType::Default(None), voting_start_epoch, - voting_end_epoch: 28.into(), - grace_epoch: 34.into(), + voting_end_epoch: voting_start_epoch + 3_u64, + grace_epoch: voting_start_epoch + 9_u64, }, None, Some(vec![content_section]), @@ -682,26 +683,38 @@ impl Client for BenchShell { impl Default for BenchShieldedCtx { fn default() -> Self { let mut shell = BenchShell::default(); + let base_dir = shell.tempdir.as_ref().canonicalize().unwrap(); + + // Create a global config and an empty wallet in the chain dir - this is + // needed in `Context::new` + let config = GlobalConfig::new(shell.inner.chain_id.clone()); + config.write(&base_dir).unwrap(); + let wallet = crate::wallet::CliWalletUtils::new( + base_dir.join(shell.inner.chain_id.as_str()), + ); + wallet.save().unwrap(); - let mut ctx = Context::new::(crate::cli::args::Global { - chain_id: None, - base_dir: shell.tempdir.as_ref().canonicalize().unwrap(), + let ctx = Context::new::(crate::cli::args::Global { + chain_id: Some(shell.inner.chain_id.clone()), + base_dir, wasm_dir: Some(WASM_DIR.into()), }) .unwrap(); + let mut chain_ctx = ctx.take_chain_or_exit(); + // Generate spending key for Albert and Bertha - ctx.wallet.gen_spending_key( + chain_ctx.wallet.gen_spending_key( ALBERT_SPENDING_KEY.to_string(), None, true, ); - ctx.wallet.gen_spending_key( + chain_ctx.wallet.gen_spending_key( BERTHA_SPENDING_KEY.to_string(), None, true, ); - crate::wallet::save(&ctx.wallet).unwrap(); + crate::wallet::save(&chain_ctx.wallet).unwrap(); // Generate payment addresses for both Albert and Bertha for (alias, viewing_alias) in [ @@ -711,19 +724,21 @@ impl Default for BenchShieldedCtx { .map(|(p, s)| (p.to_owned(), s.to_owned())) { let viewing_key: FromContext = FromContext::new( - ctx.wallet + chain_ctx + .wallet .find_viewing_key(viewing_alias) .unwrap() .to_string(), ); - let viewing_key = - ExtendedFullViewingKey::from(ctx.get_cached(&viewing_key)) - .fvk - .vk; + let viewing_key = ExtendedFullViewingKey::from( + chain_ctx.get_cached(&viewing_key), + ) + .fvk + .vk; let (div, _g_d) = namada_sdk::masp::find_valid_diversifier(&mut OsRng); let payment_addr = viewing_key.to_payment_address(div).unwrap(); - let _ = ctx + let _ = chain_ctx .wallet .insert_payment_addr( alias, @@ -733,7 +748,7 @@ impl Default for BenchShieldedCtx { .unwrap(); } - crate::wallet::save(&ctx.wallet).unwrap(); + crate::wallet::save(&chain_ctx.wallet).unwrap(); namada::ledger::storage::update_allowed_conversions( &mut shell.wl_storage, ) @@ -742,7 +757,7 @@ impl Default for BenchShieldedCtx { Self { shielded: ShieldedContext::default(), shell, - wallet: ctx.wallet, + wallet: chain_ctx.wallet, } } } diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 51e3b03a73..b9b4d15780 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -712,7 +712,7 @@ pub mod cmds { /// List all known payment addresses #[derive(Clone, Debug)] - pub struct MaspListPayAddrs; + pub struct MaspListPayAddrs(pub args::MaspListPayAddrs); impl SubCmd for MaspListPayAddrs { const CMD: &'static str = "list-addrs"; @@ -720,12 +720,13 @@ pub mod cmds { fn parse(matches: &ArgMatches) -> Option { matches .subcommand_matches(Self::CMD) - .map(|_matches| MaspListPayAddrs) + .map(|matches| Self(args::MaspListPayAddrs::parse(matches))) } fn def() -> App { App::new(Self::CMD) .about("Lists all payment addresses in the wallet") + .add_args::() } } @@ -903,7 +904,7 @@ pub mod cmds { /// List known addresses #[derive(Clone, Debug)] - pub struct AddressList; + pub struct AddressList(pub args::AddressList); impl SubCmd for AddressList { const CMD: &'static str = "list"; @@ -911,11 +912,13 @@ pub mod cmds { fn parse(matches: &ArgMatches) -> Option { matches .subcommand_matches(Self::CMD) - .map(|_matches| AddressList) + .map(|matches| Self(args::AddressList::parse(matches))) } fn def() -> App { - App::new(Self::CMD).about("List all known addresses.") + App::new(Self::CMD) + .about("List all known addresses.") + .add_args::() } } @@ -1959,6 +1962,8 @@ pub mod cmds { PkToTmAddress(PkToTmAddress), DefaultBaseDir(DefaultBaseDir), EpochSleep(EpochSleep), + ValidateGenesisTemplates(ValidateGenesisTemplates), + SignGenesisTx(SignGenesisTx), } impl SubCmd for Utils { @@ -1980,6 +1985,10 @@ pub mod cmds { let default_base_dir = SubCmd::parse(matches).map(Self::DefaultBaseDir); let epoch_sleep = SubCmd::parse(matches).map(Self::EpochSleep); + let validate_genesis_templates = + SubCmd::parse(matches).map(Self::ValidateGenesisTemplates); + let genesis_tx = + SubCmd::parse(matches).map(Self::SignGenesisTx); join_network .or(fetch_wasms) .or(validate_wasm) @@ -1988,6 +1997,8 @@ pub mod cmds { .or(pk_to_tm_address) .or(default_base_dir) .or(epoch_sleep) + .or(validate_genesis_templates) + .or(genesis_tx) }) } @@ -2002,6 +2013,8 @@ pub mod cmds { .subcommand(PkToTmAddress::def()) .subcommand(DefaultBaseDir::def()) .subcommand(EpochSleep::def()) + .subcommand(ValidateGenesisTemplates::def()) + .subcommand(SignGenesisTx::def()) .subcommand_required(true) .arg_required_else_help(true) } @@ -2109,6 +2122,44 @@ pub mod cmds { } } + #[derive(Clone, Debug)] + pub struct ValidateGenesisTemplates(pub args::ValidateGenesisTemplates); + + impl SubCmd for ValidateGenesisTemplates { + const CMD: &'static str = "validate-genesis-templates"; + + fn parse(matches: &ArgMatches) -> Option { + matches.subcommand_matches(Self::CMD).map(|matches| { + Self(args::ValidateGenesisTemplates::parse(matches)) + }) + } + + fn def() -> App { + App::new(Self::CMD) + .about("Validate genesis templates.") + .add_args::() + } + } + + #[derive(Clone, Debug)] + pub struct SignGenesisTx(pub args::SignGenesisTx); + + impl SubCmd for SignGenesisTx { + const CMD: &'static str = "sign-genesis-tx"; + + fn parse(matches: &ArgMatches) -> Option { + matches + .subcommand_matches(Self::CMD) + .map(|matches| Self(args::SignGenesisTx::parse(matches))) + } + + fn def() -> App { + App::new(Self::CMD) + .about("Sign genesis transaction(s).") + .add_args::() + } + } + /// Used as sub-commands (`SubCmd` instance) in `namadar` binary. #[derive(Clone, Debug)] pub enum EthBridgePool { @@ -2566,6 +2617,7 @@ pub mod args { use std::collections::HashMap; use std::convert::TryFrom; use std::env; + use std::net::SocketAddr; use std::path::PathBuf; use std::str::FromStr; @@ -2587,7 +2639,7 @@ pub mod args { use super::context::*; use super::utils::*; use super::{ArgGroup, ArgMatches}; - use crate::cli::context::FromContext; + use crate::client::utils::PRE_GENESIS_DIR; use crate::config::{self, Action, ActionAtHeight}; use crate::facade::tendermint::Timeout; use crate::facade::tendermint_config::net::Address as TendermintAddress; @@ -2708,6 +2760,7 @@ pub mod args { }), ); pub const GENESIS_PATH: Arg = arg("genesis-path"); + pub const GENESIS_TIME: Arg = arg("genesis-time"); pub const GENESIS_VALIDATOR: ArgOpt = arg("genesis-validator").opt(); pub const HALT_ACTION: ArgFlag = flag("halt"); @@ -2733,20 +2786,23 @@ pub mod args { arg("max-commission-rate-change"); pub const MAX_ETH_GAS: ArgOpt = arg_opt("max_eth-gas"); pub const MODE: ArgOpt = arg_opt("mode"); - pub const NET_ADDRESS: Arg = arg("net-address"); + pub const NET_ADDRESS: Arg = arg("net-address"); pub const NAMADA_START_TIME: ArgOpt = arg_opt("time"); pub const NO_CONVERSIONS: ArgFlag = flag("no-conversions"); pub const NUT: ArgFlag = flag("nut"); pub const OUT_FILE_PATH_OPT: ArgOpt = arg_opt("out-file-path"); + pub const OUTPUT: ArgOpt = arg_opt("output"); pub const OUTPUT_FOLDER_PATH: ArgOpt = arg_opt("output-folder-path"); pub const OWNER: Arg = arg("owner"); pub const OWNER_OPT: ArgOpt = OWNER.opt(); + pub const PATH: Arg = arg("path"); pub const PIN: ArgFlag = flag("pin"); pub const PORT_ID: ArgDefault = arg_default( "port-id", DefaultFn(|| PortId::from_str("transfer").unwrap()), ); + pub const PRE_GENESIS: ArgFlag = flag("pre-genesis"); pub const PROPOSAL_ETH: ArgFlag = flag("eth"); pub const PROPOSAL_PGF_STEWARD: ArgFlag = flag("pgf-stewards"); pub const PROPOSAL_PGF_FUNDING: ArgFlag = flag("pgf-funding"); @@ -2765,12 +2821,18 @@ pub mod args { pub const RAW_PUBLIC_KEY: Arg = arg("public-key"); pub const RAW_PUBLIC_KEY_OPT: ArgOpt = arg_opt("public-key"); + pub const RAW_SOURCE: Arg = arg("source"); pub const RECEIVER: Arg = arg("receiver"); pub const RELAYER: Arg
= arg("relayer"); pub const SAFE_MODE: ArgFlag = flag("safe-mode"); pub const SCHEME: ArgDefault = arg_default("scheme", DefaultFn(|| SchemeType::Ed25519)); + pub const SELF_BOND_AMOUNT: Arg = + arg("self-bond-amount"); pub const SENDER: Arg = arg("sender"); + pub const SIGNER: ArgOpt = arg_opt("signer"); + pub const SIGNING_KEY_OPT: ArgOpt = SIGNING_KEY.opt(); + pub const SIGNING_KEY: Arg = arg("signing-key"); pub const SIGNING_KEYS: ArgMulti = arg_multi("signing-keys"); pub const SIGNATURES: ArgMulti = arg_multi("signatures"); pub const SOURCE: Arg = arg("source"); @@ -2779,11 +2841,14 @@ pub mod args { pub const SOURCE_VALIDATOR: Arg = arg("source-validator"); pub const STORAGE_KEY: Arg = arg("storage-key"); pub const SUSPEND_ACTION: ArgFlag = flag("suspend"); + pub const TEMPLATES_PATH: Arg = arg("templates-path"); pub const TIMEOUT_HEIGHT: ArgOpt = arg_opt("timeout-height"); pub const TIMEOUT_SEC_OFFSET: ArgOpt = arg_opt("timeout-sec-offset"); pub const TM_ADDRESS: Arg = arg("tm-address"); pub const TOKEN_OPT: ArgOpt = TOKEN.opt(); pub const TOKEN: Arg = arg("token"); + pub const TRANSFER_FROM_SOURCE_AMOUNT: Arg = + arg("transfer-from-source-amount"); pub const TRANSFER_SOURCE: Arg = arg("source"); pub const TRANSFER_TARGET: Arg = arg("target"); pub const TX_HASH: Arg = arg("tx-hash"); @@ -3013,16 +3078,20 @@ pub mod args { impl CliToSdk> for EthereumBridgePool { fn to_sdk(self, ctx: &mut Context) -> EthereumBridgePool { + let tx = self.tx.to_sdk(ctx); + let chain_ctx = ctx.borrow_chain_or_exit(); EthereumBridgePool:: { nut: self.nut, - tx: self.tx.to_sdk(ctx), + tx, asset: self.asset, recipient: self.recipient, - sender: ctx.get(&self.sender), + sender: chain_ctx.get(&self.sender), amount: self.amount, fee_amount: self.fee_amount, - fee_payer: self.fee_payer.map(|fee_payer| ctx.get(&fee_payer)), - fee_token: ctx.get(&self.fee_token), + fee_payer: self + .fee_payer + .map(|fee_payer| chain_ctx.get(&fee_payer)), + fee_token: chain_ctx.get(&self.fee_token), code_path: self.code_path, } } @@ -3096,6 +3165,7 @@ pub mod args { impl CliToSdk> for RecommendBatch { fn to_sdk(self, ctx: &mut Context) -> RecommendBatch { + let chain_ctx = ctx.borrow_chain_or_exit(); RecommendBatch:: { query: self.query.to_sdk_ctxless(), max_gas: self.max_gas, @@ -3115,8 +3185,8 @@ pub mod args { .map(|(token, conversion_rate)| { let token_from_ctx = FromContext::
::new(token); - let address = ctx.get(&token_from_ctx); - let alias = token_from_ctx.into_raw(); + let address = chain_ctx.get(&token_from_ctx); + let alias = token_from_ctx.raw; ( address, BpConversionTableEntry { @@ -3500,7 +3570,7 @@ pub mod args { serialized_tx: self.serialized_tx.map(|path| { std::fs::read(path).expect("Expected a file at given path") }), - owner: ctx.get(&self.owner), + owner: ctx.borrow_chain_or_exit().get(&self.owner), } } } @@ -3558,13 +3628,15 @@ pub mod args { impl CliToSdk> for TxTransfer { fn to_sdk(self, ctx: &mut Context) -> TxTransfer { + let tx = self.tx.to_sdk(ctx); + let chain_ctx = ctx.borrow_mut_chain_or_exit(); TxTransfer:: { - tx: self.tx.to_sdk(ctx), - source: ctx.get_cached(&self.source), - target: ctx.get(&self.target), - token: ctx.get(&self.token), + tx, + source: chain_ctx.get_cached(&self.source), + target: chain_ctx.get(&self.target), + token: chain_ctx.get(&self.token), amount: self.amount, - native_token: ctx.native_token.clone(), + native_token: chain_ctx.native_token.clone(), tx_code_path: self.tx_code_path.to_path_buf(), } } @@ -3606,11 +3678,13 @@ pub mod args { impl CliToSdk> for TxIbcTransfer { fn to_sdk(self, ctx: &mut Context) -> TxIbcTransfer { + let tx = self.tx.to_sdk(ctx); + let chain_ctx = ctx.borrow_mut_chain_or_exit(); TxIbcTransfer:: { - tx: self.tx.to_sdk(ctx), - source: ctx.get(&self.source), + tx, + source: chain_ctx.get(&self.source), receiver: self.receiver, - token: ctx.get(&self.token), + token: chain_ctx.get(&self.token), amount: self.amount, port_id: self.port_id, channel_id: self.channel_id, @@ -3682,14 +3756,16 @@ pub mod args { impl CliToSdk> for TxInitAccount { fn to_sdk(self, ctx: &mut Context) -> TxInitAccount { + let tx = self.tx.to_sdk(ctx); + let chain_ctx = ctx.borrow_mut_chain_or_exit(); TxInitAccount:: { - tx: self.tx.to_sdk(ctx), + tx, vp_code_path: self.vp_code_path, tx_code_path: self.tx_code_path, public_keys: self .public_keys .iter() - .map(|pk| ctx.get_cached(pk)) + .map(|pk| chain_ctx.get_cached(pk)) .collect(), threshold: self.threshold, } @@ -3735,19 +3811,27 @@ pub mod args { impl CliToSdk> for TxInitValidator { fn to_sdk(self, ctx: &mut Context) -> TxInitValidator { + let tx = self.tx.to_sdk(ctx); + let chain_ctx = ctx.borrow_mut_chain_or_exit(); TxInitValidator:: { - tx: self.tx.to_sdk(ctx), + tx, scheme: self.scheme, account_keys: self .account_keys .iter() - .map(|x| ctx.get_cached(x)) + .map(|x| chain_ctx.get_cached(x)) .collect(), threshold: self.threshold, - consensus_key: self.consensus_key.map(|x| ctx.get_cached(&x)), - eth_cold_key: self.eth_cold_key.map(|x| ctx.get_cached(&x)), - eth_hot_key: self.eth_hot_key.map(|x| ctx.get_cached(&x)), - protocol_key: self.protocol_key.map(|x| ctx.get_cached(&x)), + consensus_key: self + .consensus_key + .map(|x| chain_ctx.get_cached(&x)), + eth_cold_key: self + .eth_cold_key + .map(|x| chain_ctx.get_cached(&x)), + eth_hot_key: self.eth_hot_key.map(|x| chain_ctx.get_cached(&x)), + protocol_key: self + .protocol_key + .map(|x| chain_ctx.get_cached(&x)), commission_rate: self.commission_rate, max_commission_rate_change: self.max_commission_rate_change, validator_vp_code_path: self @@ -3854,15 +3938,17 @@ pub mod args { impl CliToSdk> for TxUpdateAccount { fn to_sdk(self, ctx: &mut Context) -> TxUpdateAccount { + let tx = self.tx.to_sdk(ctx); + let chain_ctx = ctx.borrow_mut_chain_or_exit(); TxUpdateAccount:: { - tx: self.tx.to_sdk(ctx), + tx, vp_code_path: self.vp_code_path, tx_code_path: self.tx_code_path, - addr: ctx.get(&self.addr), + addr: chain_ctx.get(&self.addr), public_keys: self .public_keys .iter() - .map(|pk| ctx.get_cached(pk)) + .map(|pk| chain_ctx.get_cached(pk)) .collect(), threshold: self.threshold, } @@ -3912,12 +3998,14 @@ pub mod args { impl CliToSdk> for Bond { fn to_sdk(self, ctx: &mut Context) -> Bond { + let tx = self.tx.to_sdk(ctx); + let chain_ctx = ctx.borrow_chain_or_exit(); Bond:: { - tx: self.tx.to_sdk(ctx), - validator: ctx.get(&self.validator), + tx, + validator: chain_ctx.get(&self.validator), amount: self.amount, - source: self.source.map(|x| ctx.get(&x)), - native_token: ctx.native_token.clone(), + source: self.source.map(|x| chain_ctx.get(&x)), + native_token: chain_ctx.native_token.clone(), tx_code_path: self.tx_code_path.to_path_buf(), } } @@ -3961,11 +4049,13 @@ pub mod args { impl CliToSdk> for Unbond { fn to_sdk(self, ctx: &mut Context) -> Unbond { + let tx = self.tx.to_sdk(ctx); + let chain_ctx = ctx.borrow_chain_or_exit(); Unbond:: { - tx: self.tx.to_sdk(ctx), - validator: ctx.get(&self.validator), + tx, + validator: chain_ctx.get(&self.validator), amount: self.amount, - source: self.source.map(|x| ctx.get(&x)), + source: self.source.map(|x| chain_ctx.get(&x)), tx_code_path: self.tx_code_path.to_path_buf(), } } @@ -4020,7 +4110,7 @@ pub mod args { ) -> UpdateStewardCommission { UpdateStewardCommission:: { tx: self.tx.to_sdk(ctx), - steward: ctx.get(&self.steward), + steward: ctx.borrow_chain_or_exit().get(&self.steward), commission: std::fs::read(self.commission).expect(""), tx_code_path: self.tx_code_path.to_path_buf(), } @@ -4056,7 +4146,7 @@ pub mod args { fn to_sdk(self, ctx: &mut Context) -> ResignSteward { ResignSteward:: { tx: self.tx.to_sdk(ctx), - steward: ctx.get(&self.steward), + steward: ctx.borrow_chain_or_exit().get(&self.steward), tx_code_path: self.tx_code_path.to_path_buf(), } } @@ -4082,11 +4172,13 @@ pub mod args { impl CliToSdk> for Redelegate { fn to_sdk(self, ctx: &mut Context) -> Redelegate { + let tx = self.tx.to_sdk(ctx); + let chain_ctx = ctx.borrow_chain_or_exit(); Redelegate:: { - tx: self.tx.to_sdk(ctx), - src_validator: ctx.get(&self.src_validator), - dest_validator: ctx.get(&self.dest_validator), - owner: ctx.get(&self.owner), + tx, + src_validator: chain_ctx.get(&self.src_validator), + dest_validator: chain_ctx.get(&self.dest_validator), + owner: chain_ctx.get(&self.owner), amount: self.amount, tx_code_path: self.tx_code_path.to_path_buf(), } @@ -4145,7 +4237,7 @@ pub mod args { is_offline: self.is_offline, is_pgf_stewards: self.is_pgf_stewards, is_pgf_funding: self.is_pgf_funding, - native_token: ctx.native_token.clone(), + native_token: ctx.borrow_chain_or_exit().native_token.clone(), tx_code_path: self.tx_code_path, } } @@ -4231,7 +4323,7 @@ pub mod args { tx: self.tx.to_sdk(ctx), proposal_id: self.proposal_id, vote: self.vote, - voter: ctx.get(&self.voter), + voter: ctx.borrow_chain_or_exit().get(&self.voter), is_offline: self.is_offline, proposal_data: self.proposal_data.map(|path| { std::fs::read(path) @@ -4301,9 +4393,11 @@ pub mod args { impl CliToSdk> for RevealPk { fn to_sdk(self, ctx: &mut Context) -> RevealPk { + let tx = self.tx.to_sdk(ctx); + let chain_ctx = ctx.borrow_mut_chain_or_exit(); RevealPk:: { - tx: self.tx.to_sdk(ctx), - public_key: ctx.get_cached(&self.public_key), + tx, + public_key: chain_ctx.get_cached(&self.public_key), } } } @@ -4464,10 +4558,12 @@ pub mod args { impl CliToSdk> for Withdraw { fn to_sdk(self, ctx: &mut Context) -> Withdraw { + let tx = self.tx.to_sdk(ctx); + let chain_ctx = ctx.borrow_chain_or_exit(); Withdraw:: { - tx: self.tx.to_sdk(ctx), - validator: ctx.get(&self.validator), - source: self.source.map(|x| ctx.get(&x)), + tx, + validator: chain_ctx.get(&self.validator), + source: self.source.map(|x| chain_ctx.get(&x)), tx_code_path: self.tx_code_path.to_path_buf(), } } @@ -4502,7 +4598,7 @@ pub mod args { fn to_sdk(self, ctx: &mut Context) -> QueryConversions { QueryConversions:: { query: self.query.to_sdk(ctx), - token: self.token.map(|x| ctx.get(&x)), + token: self.token.map(|x| ctx.borrow_chain_or_exit().get(&x)), epoch: self.epoch, } } @@ -4539,7 +4635,7 @@ pub mod args { fn to_sdk(self, ctx: &mut Context) -> QueryAccount { QueryAccount:: { query: self.query.to_sdk(ctx), - owner: ctx.get(&self.owner), + owner: ctx.borrow_chain_or_exit().get(&self.owner), } } } @@ -4563,10 +4659,12 @@ pub mod args { impl CliToSdk> for QueryBalance { fn to_sdk(self, ctx: &mut Context) -> QueryBalance { + let query = self.query.to_sdk(ctx); + let chain_ctx = ctx.borrow_mut_chain_or_exit(); QueryBalance:: { - query: self.query.to_sdk(ctx), - owner: self.owner.map(|x| ctx.get_cached(&x)), - token: self.token.map(|x| ctx.get(&x)), + query, + owner: self.owner.map(|x| chain_ctx.get_cached(&x)), + token: self.token.map(|x| chain_ctx.get(&x)), no_conversions: self.no_conversions, } } @@ -4608,10 +4706,12 @@ pub mod args { impl CliToSdk> for QueryTransfers { fn to_sdk(self, ctx: &mut Context) -> QueryTransfers { + let query = self.query.to_sdk(ctx); + let chain_ctx = ctx.borrow_mut_chain_or_exit(); QueryTransfers:: { - query: self.query.to_sdk(ctx), - owner: self.owner.map(|x| ctx.get_cached(&x)), - token: self.token.map(|x| ctx.get(&x)), + query, + owner: self.owner.map(|x| chain_ctx.get_cached(&x)), + token: self.token.map(|x| chain_ctx.get(&x)), } } } @@ -4641,10 +4741,12 @@ pub mod args { impl CliToSdk> for QueryBonds { fn to_sdk(self, ctx: &mut Context) -> QueryBonds { + let query = self.query.to_sdk(ctx); + let chain_ctx = ctx.borrow_chain_or_exit(); QueryBonds:: { - query: self.query.to_sdk(ctx), - owner: self.owner.map(|x| ctx.get(&x)), - validator: self.validator.map(|x| ctx.get(&x)), + query, + owner: self.owner.map(|x| chain_ctx.get(&x)), + validator: self.validator.map(|x| chain_ctx.get(&x)), } } } @@ -4680,7 +4782,9 @@ pub mod args { fn to_sdk(self, ctx: &mut Context) -> QueryBondedStake { QueryBondedStake:: { query: self.query.to_sdk(ctx), - validator: self.validator.map(|x| ctx.get(&x)), + validator: self + .validator + .map(|x| ctx.borrow_chain_or_exit().get(&x)), epoch: self.epoch, } } @@ -4714,7 +4818,7 @@ pub mod args { fn to_sdk(self, ctx: &mut Context) -> QueryValidatorState { QueryValidatorState:: { query: self.query.to_sdk(ctx), - validator: ctx.get(&self.validator), + validator: ctx.borrow_chain_or_exit().get(&self.validator), epoch: self.epoch, } } @@ -4752,7 +4856,7 @@ pub mod args { fn to_sdk(self, ctx: &mut Context) -> CommissionRateChange { CommissionRateChange:: { tx: self.tx.to_sdk(ctx), - validator: ctx.get(&self.validator), + validator: ctx.borrow_chain_or_exit().get(&self.validator), rate: self.rate, tx_code_path: self.tx_code_path.to_path_buf(), } @@ -4790,7 +4894,7 @@ pub mod args { fn to_sdk(self, ctx: &mut Context) -> TxUnjailValidator { TxUnjailValidator:: { tx: self.tx.to_sdk(ctx), - validator: ctx.get(&self.validator), + validator: ctx.borrow_chain_or_exit().get(&self.validator), tx_code_path: self.tx_code_path.to_path_buf(), } } @@ -4822,7 +4926,7 @@ pub mod args { SignTx:: { tx: self.tx.to_sdk(ctx), tx_data: std::fs::read(self.tx_data).expect(""), - owner: ctx.get(&self.owner), + owner: ctx.borrow_chain_or_exit().get(&self.owner), } } } @@ -4857,11 +4961,13 @@ pub mod args { self, ctx: &mut Context, ) -> GenIbcShieldedTransafer { + let query = self.query.to_sdk(ctx); + let chain_ctx = ctx.borrow_chain_or_exit(); GenIbcShieldedTransafer:: { - query: self.query.to_sdk(ctx), + query, output_folder: self.output_folder, - target: ctx.get(&self.target), - token: ctx.get(&self.token), + target: chain_ctx.get(&self.target), + token: chain_ctx.get(&self.token), amount: self.amount, port_id: self.port_id, channel_id: self.channel_id, @@ -4914,7 +5020,7 @@ pub mod args { fn to_sdk(self, ctx: &mut Context) -> QueryCommissionRate { QueryCommissionRate:: { query: self.query.to_sdk(ctx), - validator: ctx.get(&self.validator), + validator: ctx.borrow_chain_or_exit().get(&self.validator), epoch: self.epoch, } } @@ -4948,7 +5054,9 @@ pub mod args { fn to_sdk(self, ctx: &mut Context) -> QuerySlashes { QuerySlashes:: { query: self.query.to_sdk(ctx), - validator: self.validator.map(|x| ctx.get(&x)), + validator: self + .validator + .map(|x| ctx.borrow_chain_or_exit().get(&x)), } } } @@ -4989,7 +5097,7 @@ pub mod args { fn to_sdk(self, ctx: &mut Context) -> QueryDelegations { QueryDelegations:: { query: self.query.to_sdk(ctx), - owner: ctx.get(&self.owner), + owner: ctx.borrow_chain_or_exit().get(&self.owner), } } } @@ -5062,6 +5170,7 @@ pub mod args { impl CliToSdk> for Tx { fn to_sdk(self, ctx: &mut Context) -> Tx { + let ctx = ctx.borrow_mut_chain_or_exit(); Tx:: { dry_run: self.dry_run, dry_run_wrapper: self.dry_run_wrapper, @@ -5308,11 +5417,13 @@ pub mod args { let alias = ALIAS.parse(matches); let alias_force = ALIAS_FORCE.parse(matches); let value = MASP_VALUE.parse(matches); + let is_pre_genesis = PRE_GENESIS.parse(matches); let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); Self { alias, alias_force, value, + is_pre_genesis, unsafe_dont_encrypt, } } @@ -5331,6 +5442,10 @@ pub mod args { .def() .help("A spending key, viewing key, or payment address."), ) + .arg(PRE_GENESIS.def().help( + "Use pre-genesis wallet, instead of for the current chain, if \ + any.", + )) .arg(UNSAFE_DONT_ENCRYPT.def().help( "UNSAFE: Do not encrypt the keypair. Do not use this for keys \ used in a live network.", @@ -5342,10 +5457,12 @@ pub mod args { fn parse(matches: &ArgMatches) -> Self { let alias = ALIAS.parse(matches); let alias_force = ALIAS_FORCE.parse(matches); + let is_pre_genesis = PRE_GENESIS.parse(matches); let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); Self { alias, alias_force, + is_pre_genesis, unsafe_dont_encrypt, } } @@ -5359,6 +5476,10 @@ pub mod args { .arg(ALIAS_FORCE.def().help( "Override the alias without confirmation if it already exists.", )) + .arg(PRE_GENESIS.def().help( + "Use pre-genesis wallet, instead of for the current chain, if \ + any.", + )) .arg(UNSAFE_DONT_ENCRYPT.def().help( "UNSAFE: Do not encrypt the keypair. Do not use this for keys \ used in a live network.", @@ -5368,11 +5489,35 @@ pub mod args { impl CliToSdk> for MaspPayAddrGen { fn to_sdk(self, ctx: &mut Context) -> MaspPayAddrGen { + use namada_sdk::wallet::Wallet; + + use crate::wallet::CliWalletUtils; + + let find_viewing_key = |w: &mut Wallet| { + w.find_viewing_key(&self.viewing_key.raw) + .map(Clone::clone) + .unwrap_or_else(|_| { + eprintln!( + "Unknown viewing key {}", + self.viewing_key.raw + ); + safe_exit(1) + }) + }; + let viewing_key = if self.is_pre_genesis || ctx.chain.is_none() { + let wallet_path = + ctx.global_args.base_dir.join(PRE_GENESIS_DIR); + let mut wallet = crate::wallet::load_or_new(&wallet_path); + find_viewing_key(&mut wallet) + } else { + find_viewing_key(&mut ctx.borrow_mut_chain_or_exit().wallet) + }; MaspPayAddrGen:: { alias: self.alias, alias_force: self.alias_force, - viewing_key: ctx.get_cached(&self.viewing_key), + viewing_key, pin: self.pin, + is_pre_genesis: self.is_pre_genesis, } } } @@ -5383,11 +5528,13 @@ pub mod args { let alias_force = ALIAS_FORCE.parse(matches); let viewing_key = VIEWING_KEY.parse(matches); let pin = PIN.parse(matches); + let is_pre_genesis = PRE_GENESIS.parse(matches); Self { alias, alias_force, viewing_key, pin, + is_pre_genesis, } } @@ -5405,6 +5552,10 @@ pub mod args { "Require that the single transaction to this address be \ pinned.", )) + .arg(PRE_GENESIS.def().help( + "Use pre-genesis wallet, instead of for the current chain, if \ + any.", + )) } } @@ -5459,12 +5610,14 @@ pub mod args { let scheme = SCHEME.parse(matches); let alias = ALIAS_OPT.parse(matches); let alias_force = ALIAS_FORCE.parse(matches); + let is_pre_genesis = PRE_GENESIS.parse(matches); let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); let derivation_path = HD_WALLET_DERIVATION_PATH_OPT.parse(matches); Self { scheme, alias, alias_force, + is_pre_genesis, unsafe_dont_encrypt, derivation_path, } @@ -5483,6 +5636,10 @@ pub mod args { .arg(ALIAS_FORCE.def().help( "Override the alias without confirmation if it already exists.", )) + .arg(PRE_GENESIS.def().help( + "Generate a key for pre-genesis, instead of for the current \ + chain, if any.", + )) .arg(UNSAFE_DONT_ENCRYPT.def().help( "UNSAFE: Do not encrypt the keypair. Do not use this for keys \ used in a live network.", @@ -5504,12 +5661,14 @@ pub mod args { let public_key = RAW_PUBLIC_KEY_OPT.parse(matches); let alias = ALIAS_OPT.parse(matches); let value = VALUE.parse(matches); + let is_pre_genesis = PRE_GENESIS.parse(matches); let unsafe_show_secret = UNSAFE_SHOW_SECRET.parse(matches); Self { public_key, alias, value, + is_pre_genesis, unsafe_show_secret, } } @@ -5532,6 +5691,10 @@ pub mod args { .def() .help("A public key or alias associated with the keypair."), ) + .arg(PRE_GENESIS.def().help( + "Use pre-genesis wallet, instead of for the current chain, if \ + any.", + )) .arg( UNSAFE_SHOW_SECRET .def() @@ -5544,9 +5707,11 @@ pub mod args { fn parse(matches: &ArgMatches) -> Self { let alias = ALIAS.parse(matches); let unsafe_show_secret = UNSAFE_SHOW_SECRET.parse(matches); + let is_pre_genesis = PRE_GENESIS.parse(matches); Self { alias, unsafe_show_secret, + is_pre_genesis, } } @@ -5557,21 +5722,31 @@ pub mod args { .def() .help("UNSAFE: Print the spending key values."), ) + .arg(PRE_GENESIS.def().help( + "Use pre-genesis wallet, instead of for the current \ + chain, if any.", + )) } } impl Args for MaspKeysList { fn parse(matches: &ArgMatches) -> Self { let decrypt = DECRYPT.parse(matches); + let is_pre_genesis = PRE_GENESIS.parse(matches); let unsafe_show_secret = UNSAFE_SHOW_SECRET.parse(matches); Self { decrypt, + is_pre_genesis, unsafe_show_secret, } } fn def(app: App) -> App { app.arg(DECRYPT.def().help("Decrypt keys that are encrypted.")) + .arg(PRE_GENESIS.def().help( + "Use pre-genesis wallet, instead of for the current \ + chain, if any.", + )) .arg( UNSAFE_SHOW_SECRET .def() @@ -5580,18 +5755,38 @@ pub mod args { } } + impl Args for MaspListPayAddrs { + fn parse(matches: &ArgMatches) -> Self { + let is_pre_genesis = PRE_GENESIS.parse(matches); + Self { is_pre_genesis } + } + + fn def(app: App) -> App { + app.arg(PRE_GENESIS.def().help( + "Use pre-genesis wallet, instead of for the current chain, if \ + any.", + )) + } + } + impl Args for KeyList { fn parse(matches: &ArgMatches) -> Self { let decrypt = DECRYPT.parse(matches); + let is_pre_genesis = PRE_GENESIS.parse(matches); let unsafe_show_secret = UNSAFE_SHOW_SECRET.parse(matches); Self { decrypt, + is_pre_genesis, unsafe_show_secret, } } fn def(app: App) -> App { app.arg(DECRYPT.def().help("Decrypt keys that are encrypted.")) + .arg(PRE_GENESIS.def().help( + "Use pre-genesis wallet, instead of for the current \ + chain, if any.", + )) .arg( UNSAFE_SHOW_SECRET .def() @@ -5603,14 +5798,21 @@ pub mod args { impl Args for KeyExport { fn parse(matches: &ArgMatches) -> Self { let alias = ALIAS.parse(matches); - - Self { alias } + let is_pre_genesis = PRE_GENESIS.parse(matches); + Self { + alias, + is_pre_genesis, + } } fn def(app: App) -> App { app.arg( ALIAS.def().help("The alias of the key you wish to export."), ) + .arg(PRE_GENESIS.def().help( + "Use pre-genesis wallet, instead of for the current chain, if \ + any.", + )) } } @@ -5618,7 +5820,12 @@ pub mod args { fn parse(matches: &ArgMatches) -> Self { let alias = ALIAS_OPT.parse(matches); let address = RAW_ADDRESS_OPT.parse(matches); - Self { alias, address } + let is_pre_genesis = PRE_GENESIS.parse(matches); + Self { + alias, + address, + is_pre_genesis, + } } fn def(app: App) -> App { @@ -5632,6 +5839,10 @@ pub mod args { .def() .help("The bech32m encoded address string."), ) + .arg(PRE_GENESIS.def().help( + "Use pre-genesis wallet, instead of for the current chain, if \ + any.", + )) .group( ArgGroup::new("find_flags") .args([ALIAS_OPT.name, RAW_ADDRESS_OPT.name]) @@ -5640,15 +5851,31 @@ pub mod args { } } + impl Args for AddressList { + fn parse(matches: &ArgMatches) -> Self { + let is_pre_genesis = PRE_GENESIS.parse(matches); + Self { is_pre_genesis } + } + + fn def(app: App) -> App { + app.arg(PRE_GENESIS.def().help( + "Use pre-genesis wallet, instead of for the current chain, if \ + any.", + )) + } + } + impl Args for AddressAdd { fn parse(matches: &ArgMatches) -> Self { let alias = ALIAS.parse(matches); let alias_force = ALIAS_FORCE.parse(matches); let address = RAW_ADDRESS.parse(matches); + let is_pre_genesis = PRE_GENESIS.parse(matches); Self { alias, alias_force, address, + is_pre_genesis, } } @@ -5666,6 +5893,10 @@ pub mod args { .def() .help("The bech32m encoded address string."), ) + .arg(PRE_GENESIS.def().help( + "Use pre-genesis wallet, instead of for the current chain, if \ + any.", + )) } } @@ -5675,6 +5906,7 @@ pub mod args { pub genesis_validator: Option, pub pre_genesis_path: Option, pub dont_prefetch_wasm: bool, + pub allow_duplicate_ip: bool, } impl Args for JoinNetwork { @@ -5683,11 +5915,13 @@ pub mod args { let genesis_validator = GENESIS_VALIDATOR.parse(matches); let pre_genesis_path = PRE_GENESIS_PATH.parse(matches); let dont_prefetch_wasm = DONT_PREFETCH_WASM.parse(matches); + let allow_duplicate_ip = ALLOW_DUPLICATE_IP.parse(matches); Self { chain_id, genesis_validator, pre_genesis_path, dont_prefetch_wasm, + allow_duplicate_ip, } } @@ -5699,6 +5933,10 @@ pub mod args { .arg(DONT_PREFETCH_WASM.def().help( "Do not pre-fetch WASM.", )) + .arg(ALLOW_DUPLICATE_IP.def().help( + "Toggle to disable guard against peers connecting from the \ + same IP. This option shouldn't be used in mainnet.", + )) } } @@ -5772,48 +6010,41 @@ pub mod args { #[derive(Clone, Debug)] pub struct InitNetwork { - pub genesis_path: PathBuf, + pub templates_path: PathBuf, pub wasm_checksums_path: PathBuf, pub chain_id_prefix: ChainIdPrefix, - pub unsafe_dont_encrypt: bool, + pub genesis_time: DateTimeUtc, pub consensus_timeout_commit: Timeout, - pub localhost: bool, - pub allow_duplicate_ip: bool, pub dont_archive: bool, pub archive_dir: Option, } impl Args for InitNetwork { fn parse(matches: &ArgMatches) -> Self { - let genesis_path = GENESIS_PATH.parse(matches); + let templates_path = TEMPLATES_PATH.parse(matches); let wasm_checksums_path = WASM_CHECKSUMS_PATH.parse(matches); let chain_id_prefix = CHAIN_ID_PREFIX.parse(matches); - let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); + let genesis_time = GENESIS_TIME.parse(matches); let consensus_timeout_commit = CONSENSUS_TIMEOUT_COMMIT.parse(matches); - let localhost = LOCALHOST.parse(matches); - let allow_duplicate_ip = ALLOW_DUPLICATE_IP.parse(matches); let dont_archive = DONT_ARCHIVE.parse(matches); let archive_dir = ARCHIVE_DIR.parse(matches); Self { - genesis_path, + templates_path, wasm_checksums_path, chain_id_prefix, - unsafe_dont_encrypt, + genesis_time, consensus_timeout_commit, - localhost, - allow_duplicate_ip, dont_archive, archive_dir, } } fn def(app: App) -> App { - app.arg( - GENESIS_PATH.def().help( - "Path to the preliminary genesis configuration file.", - ), - ) + app.arg(TEMPLATES_PATH.def().help( + "Path to the directory with genesis templates to be used to \ + initialize the network.", + )) .arg( WASM_CHECKSUMS_PATH .def() @@ -5823,22 +6054,14 @@ pub mod args { "The chain ID prefix. Up to 19 alphanumeric, '.', '-' or '_' \ characters.", )) - .arg(UNSAFE_DONT_ENCRYPT.def().help( - "UNSAFE: Do not encrypt the generated keypairs. Do not use \ - this for keys used in a live network.", + .arg(GENESIS_TIME.def().help( + "The start time of the network in RFC 3339 and ISO 8601 \ + format. For example: \"2021-12-31T00:00:00Z\".", )) .arg(CONSENSUS_TIMEOUT_COMMIT.def().help( "The Tendermint consensus timeout_commit configuration as \ e.g. `1s` or `1000ms`. Defaults to 10 seconds.", )) - .arg(LOCALHOST.def().help( - "Use localhost address for P2P and RPC connections for the \ - validators ledger", - )) - .arg(ALLOW_DUPLICATE_IP.def().help( - "Toggle to disable guard against peers connecting from the \ - same IP. This option shouldn't be used in mainnet.", - )) .arg( DONT_ARCHIVE .def() @@ -5853,16 +6076,20 @@ pub mod args { #[derive(Clone, Debug)] pub struct InitGenesisValidator { + pub source: String, pub alias: String, pub commission_rate: Dec, pub max_commission_rate_change: Dec, - pub net_address: String, + pub net_address: SocketAddr, pub unsafe_dont_encrypt: bool, pub key_scheme: SchemeType, + pub transfer_from_source_amount: token::DenominatedAmount, + pub self_bond_amount: token::DenominatedAmount, } impl Args for InitGenesisValidator { fn parse(matches: &ArgMatches) -> Self { + let source = RAW_SOURCE.parse(matches); let alias = ALIAS.parse(matches); let commission_rate = COMMISSION_RATE.parse(matches); let max_commission_rate_change = @@ -5870,40 +6097,117 @@ pub mod args { let net_address = NET_ADDRESS.parse(matches); let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); let key_scheme = SCHEME.parse(matches); + // The denomination validation is handled by validating genesis + // files later. + // At this stage, we treat amounts as opaque and pass them on + // verbatim. + let transfer_from_source_amount = + TRANSFER_FROM_SOURCE_AMOUNT.parse(matches); + // this must be an amount of native tokens + let self_bond_amount = SELF_BOND_AMOUNT.parse(matches); Self { + source, alias, net_address, unsafe_dont_encrypt, key_scheme, commission_rate, max_commission_rate_change, + transfer_from_source_amount, + self_bond_amount, } } fn def(app: App) -> App { - app.arg(ALIAS.def().help("The validator address alias.")) - .arg(NET_ADDRESS.def().help( - "Static {host:port} of your validator node's P2P address. \ - Namada uses port `26656` for P2P connections by default, \ - but you can configure a different value.", - )) - .arg(COMMISSION_RATE.def().help( - "The commission rate charged by the validator for \ - delegation rewards. This is a required parameter.", - )) - .arg(MAX_COMMISSION_RATE_CHANGE.def().help( - "The maximum change per epoch in the commission rate \ - charged by the validator for delegation rewards. This is \ - a required parameter.", - )) - .arg(UNSAFE_DONT_ENCRYPT.def().help( - "UNSAFE: Do not encrypt the generated keypairs. Do not \ - use this for keys used in a live network.", - )) - .arg(SCHEME.def().help( - "The key scheme/type used for the validator keys. \ - Currently supports ed25519 and secp256k1.", - )) + app.arg(RAW_SOURCE.def().help( + "The source key for native token transfer from the \ + `balances.toml` genesis template.", + )) + .arg(ALIAS.def().help("The validator address alias.")) + .arg(NET_ADDRESS.def().help( + "Static {host:port} of your validator node's P2P address. \ + Namada uses port `26656` for P2P connections by default, but \ + you can configure a different value.", + )) + .arg(COMMISSION_RATE.def().help( + "The commission rate charged by the validator for delegation \ + rewards. This is a required parameter.", + )) + .arg(MAX_COMMISSION_RATE_CHANGE.def().help( + "The maximum change per epoch in the commission rate charged \ + by the validator for delegation rewards. This is a required \ + parameter.", + )) + .arg(UNSAFE_DONT_ENCRYPT.def().help( + "UNSAFE: Do not encrypt the generated keypairs. Do not use \ + this for keys used in a live network.", + )) + .arg(SCHEME.def().help( + "The key scheme/type used for the validator keys. Currently \ + supports ed25519 and secp256k1.", + )) + .arg(TRANSFER_FROM_SOURCE_AMOUNT.def().help( + "The amount of native token to transfer into the validator \ + account from the `--source`. To self-bond some tokens to the \ + validator at genesis, specify `--self-bond-amount`.", + )) + .arg( + SELF_BOND_AMOUNT + .def() + .help( + "The amount of native token to self-bond in PoS. \ + Because this amount will be bonded from the \ + validator's account, it must be lower or equal to \ + the amount specified in \ + `--transfer-from-source-amount`.", + ) + .requires(TRANSFER_FROM_SOURCE_AMOUNT.name), + ) + } + } + + #[derive(Clone, Debug)] + pub struct ValidateGenesisTemplates { + /// Templates dir + pub path: PathBuf, + } + + impl Args for ValidateGenesisTemplates { + fn parse(matches: &ArgMatches) -> Self { + let path = PATH.parse(matches); + Self { path } + } + + fn def(app: App) -> App { + app.arg( + PATH.def() + .help("Path to the directory with the template files."), + ) + } + } + + #[derive(Clone, Debug)] + pub struct SignGenesisTx { + pub path: PathBuf, + pub output: Option, + } + + impl Args for SignGenesisTx { + fn parse(matches: &ArgMatches) -> Self { + let path = PATH.parse(matches); + let output = OUTPUT.parse(matches); + Self { path, output } + } + + fn def(app: App) -> App { + app.arg( + PATH.def() + .help("Path to the unsigned transactions TOML file."), + ) + .arg(OUTPUT.def().help( + "Save the output to a TOML file. When not supplied, the \ + signed transactions will be printed to stdout instead.", + )) } } } diff --git a/apps/src/lib/cli/client.rs b/apps/src/lib/cli/client.rs index ec60584642..9a88626561 100644 --- a/apps/src/lib/cli/client.rs +++ b/apps/src/lib/cli/client.rs @@ -114,19 +114,21 @@ impl CliApi { }); client.wait_until_node_is_synced(io).await?; let args = args.to_sdk(&mut ctx); + let cli::context::ChainContext { + mut wallet, + mut config, + mut shielded, + native_token, + } = ctx.take_chain_or_exit(); let namada = NamadaImpl::native_new( &client, - &mut ctx.wallet, - &mut ctx.shielded, + &mut wallet, + &mut shielded, io, - ctx.native_token, + native_token, ); - tx::submit_init_validator( - &namada, - &mut ctx.config, - args, - ) - .await?; + tx::submit_init_validator(&namada, &mut config, args) + .await?; } Sub::TxInitProposal(TxInitProposal(mut args)) => { let client = client.unwrap_or_else(|| { @@ -538,6 +540,12 @@ impl CliApi { let namada = ctx.to_sdk(&client, io); rpc::epoch_sleep(&namada, args).await; } + Utils::ValidateGenesisTemplates(ValidateGenesisTemplates( + args, + )) => utils::validate_genesis_templates(global_args, args), + Utils::SignGenesisTx(SignGenesisTx(args)) => { + utils::sign_genesis_tx(global_args, args) + } }, } Ok(()) diff --git a/apps/src/lib/cli/context.rs b/apps/src/lib/cli/context.rs index 64401984ce..be0c2e3c4d 100644 --- a/apps/src/lib/cli/context.rs +++ b/apps/src/lib/cli/context.rs @@ -8,7 +8,6 @@ use std::str::FromStr; use color_eyre::eyre::Result; use namada::ledger::ibc::storage::ibc_token; use namada::types::address::{Address, InternalAddress}; -use namada::types::chain::ChainId; use namada::types::ethereum_events::EthAddress; use namada::types::ibc::is_ibc_denom; use namada::types::io::Io; @@ -20,16 +19,12 @@ use namada_sdk::wallet::Wallet; use namada_sdk::{Namada, NamadaImpl}; use super::args; -#[cfg(any(test, feature = "dev"))] -use crate::config::genesis; -use crate::config::genesis::genesis_config; +use crate::cli::utils; use crate::config::global::GlobalConfig; -use crate::config::{self, Config}; +use crate::config::{genesis, Config}; use crate::wallet::CliWalletUtils; -use crate::wasm_loader; +use crate::{wallet, wasm_loader}; -/// Env. var to set chain ID -const ENV_VAR_CHAIN_ID: &str = "NAMADA_CHAIN_ID"; /// Env. var to set wasm directory pub const ENV_VAR_WASM_DIR: &str = "NAMADA_WASM_DIR"; @@ -74,10 +69,17 @@ pub type WalletBalanceOwner = FromContext; pub struct Context { /// Global arguments pub global_args: args::Global, - /// The wallet - pub wallet: Wallet, /// The global configuration pub global_config: GlobalConfig, + /// Chain-specific context, if any chain is configured in `global_config` + pub chain: Option, +} + +/// Command execution context with chain-specific data +#[derive(Debug)] +pub struct ChainContext { + /// The wallet + pub wallet: Wallet, /// The ledger configuration for a specific chain ID pub config: Config, /// The context fr shielded operations @@ -89,70 +91,79 @@ pub struct Context { impl Context { pub fn new(global_args: args::Global) -> Result { let global_config = read_or_try_new_global_config(&global_args); - tracing::debug!("Chain ID: {}", global_config.default_chain_id); - - let mut config = Config::load( - &global_args.base_dir, - &global_config.default_chain_id, - None, - ); - - let chain_dir = global_args - .base_dir - .join(global_config.default_chain_id.as_str()); - let genesis_file_path = global_args - .base_dir - .join(format!("{}.toml", global_config.default_chain_id.as_str())); - // NOTE: workaround to make this function work both in integration tests - // and benchmarks - let (wallet, native_token) = if genesis_file_path.is_file() { - let genesis = - genesis_config::read_genesis_config(&genesis_file_path); - - let default_genesis = - genesis_config::open_genesis_config(genesis_file_path)?; - - ( - crate::wallet::load_or_new_from_genesis( - &chain_dir, - default_genesis, - ), - genesis.native_token, - ) // If the WASM dir specified, put it in the config - } else { - #[cfg(not(any(test, feature = "testing")))] - panic!("Missing genesis file"); - #[cfg(any(test, feature = "testing"))] - { - let default_genesis = genesis::genesis(1); - ( - crate::wallet::load_or_new(&genesis_file_path), - default_genesis.native_token, - ) - } - }; - match global_args.wasm_dir.as_ref() { - Some(wasm_dir) => { - config.wasm_dir = wasm_dir.clone(); - } - None => { - if let Ok(wasm_dir) = env::var(ENV_VAR_WASM_DIR) { - let wasm_dir: PathBuf = wasm_dir.into(); - config.wasm_dir = wasm_dir; + let chain = match global_config.default_chain_id.as_ref() { + Some(default_chain_id) => { + tracing::info!("Default chain ID: {default_chain_id}"); + let mut config = + Config::load(&global_args.base_dir, default_chain_id, None); + let chain_dir = + global_args.base_dir.join(default_chain_id.as_str()); + let genesis = + genesis::chain::Finalized::read_toml_files(&chain_dir) + .expect("Missing genesis files"); + let native_token = genesis.get_native_token().clone(); + let wallet = if wallet::exists(&chain_dir) { + wallet::load(&chain_dir).unwrap() + } else { + panic!( + "Could not find wallet at {}.", + chain_dir.to_string_lossy() + ); + }; + + // If the WASM dir specified, put it in the config + match global_args.wasm_dir.as_ref() { + Some(wasm_dir) => { + config.wasm_dir = wasm_dir.clone(); + } + None => { + if let Ok(wasm_dir) = env::var(ENV_VAR_WASM_DIR) { + let wasm_dir: PathBuf = wasm_dir.into(); + config.wasm_dir = wasm_dir; + } + } } + Some(ChainContext { + wallet, + config, + shielded: FsShieldedUtils::new(chain_dir), + native_token, + }) } - } + None => None, + }; + Ok(Self { global_args, - wallet, global_config, - config, - shielded: FsShieldedUtils::new(chain_dir), - native_token, + chain, }) } + /// Try to take the chain context, or exit the process with an error if no + /// chain is configured. + pub fn take_chain_or_exit(self) -> ChainContext { + self.chain + .unwrap_or_else(|| safe_exit_on_missing_chain_context()) + } + + /// Try to borrow chain context, or exit the process with an error if no + /// chain is configured. + pub fn borrow_chain_or_exit(&self) -> &ChainContext { + self.chain + .as_ref() + .unwrap_or_else(|| safe_exit_on_missing_chain_context()) + } + + /// Try to borrow mutably chain context, or exit the process with an error + /// if no chain is configured. + pub fn borrow_mut_chain_or_exit(&mut self) -> &mut ChainContext { + self.chain + .as_mut() + .unwrap_or_else(|| safe_exit_on_missing_chain_context()) + } + /// Make an implementation of Namada from this object and parameters. pub fn to_sdk<'a, C, IO>( &'a mut self, @@ -163,15 +174,26 @@ impl Context { C: namada::ledger::queries::Client + Sync, IO: Io, { + let chain_ctx = self.borrow_mut_chain_or_exit(); NamadaImpl::native_new( client, - &mut self.wallet, - &mut self.shielded, + &mut chain_ctx.wallet, + &mut chain_ctx.shielded, io, - self.native_token.clone(), + chain_ctx.native_token.clone(), ) } +} + +fn safe_exit_on_missing_chain_context() -> ! { + eprintln!( + "No chain is configured. You may need to run `namada client utils \ + join-network` command." + ); + utils::safe_exit(1) +} +impl ChainContext { /// Parse and/or look-up the value from the context. pub fn get(&self, from_context: &FromContext) -> T where @@ -216,20 +238,7 @@ impl Context { /// /// Note that in "dev" build, this may be the root `wasm` dir. pub fn wasm_dir(&self) -> PathBuf { - let wasm_dir = - self.config.ledger.chain_dir().join(&self.config.wasm_dir); - - // In dev-mode with dev chain (the default), load wasm directly from the - // root wasm dir instead of the chain dir - #[cfg(feature = "dev")] - let wasm_dir = - if self.global_config.default_chain_id == ChainId::default() { - "wasm".into() - } else { - wasm_dir - }; - - wasm_dir + self.config.ledger.chain_dir().join(&self.config.wasm_dir) } /// Read the given WASM file from the WASM directory or an absolute path. @@ -239,41 +248,20 @@ impl Context { } /// Load global config from expected path in the `base_dir` or try to generate a -/// new one if it doesn't exist. +/// new one without a chain if it doesn't exist. pub fn read_or_try_new_global_config( global_args: &args::Global, ) -> GlobalConfig { GlobalConfig::read(&global_args.base_dir).unwrap_or_else(|err| { - if let config::global::Error::FileNotFound(_) = err { - let chain_id = global_args.chain_id.clone().or_else(|| { - env::var(ENV_VAR_CHAIN_ID).ok().map(|chain_id| { - ChainId::from_str(&chain_id).unwrap_or_else(|err| { - eprintln!("Invalid chain ID: {}", err); - super::safe_exit(1) - }) - }) - }); - - // If not specified, use the default - let chain_id = chain_id.unwrap_or_default(); - - let config = GlobalConfig::new(chain_id); - config.write(&global_args.base_dir).unwrap_or_else(|err| { - tracing::error!("Error writing global config file: {}", err); - super::safe_exit(1) - }); - config - } else { - eprintln!("Error reading global config: {}", err); - super::safe_exit(1) - } + eprintln!("Error reading global config: {}", err); + super::safe_exit(1) }) } /// Argument that can be given raw or found in the [`Context`]. #[derive(Debug, Clone)] pub struct FromContext { - raw: String, + pub(crate) raw: String, phantom: PhantomData, } @@ -284,10 +272,6 @@ impl FromContext { phantom: PhantomData, } } - - pub fn into_raw(self) -> String { - self.raw - } } impl FromContext { @@ -335,8 +319,8 @@ impl FromContext where T: ArgFromContext, { - /// Parse and/or look-up the value from the context. - fn arg_from_ctx(&self, ctx: &Context) -> Result { + /// Parse and/or look-up the value from the chain context. + fn arg_from_ctx(&self, ctx: &ChainContext) -> Result { T::arg_from_ctx(ctx, &self.raw) } } @@ -345,32 +329,32 @@ impl FromContext where T: ArgFromMutContext, { - /// Parse and/or look-up the value from the mutable context. - fn arg_from_mut_ctx(&self, ctx: &mut Context) -> Result { + /// Parse and/or look-up the value from the mutable chain context. + fn arg_from_mut_ctx(&self, ctx: &mut ChainContext) -> Result { T::arg_from_mut_ctx(ctx, &self.raw) } } -/// CLI argument that found via the [`Context`]. +/// CLI argument that found via the [`ChainContext`]. pub trait ArgFromContext: Sized { fn arg_from_ctx( - ctx: &Context, + ctx: &ChainContext, raw: impl AsRef, ) -> Result; } -/// CLI argument that found via the [`Context`] and cached (as in case of an -/// encrypted keypair that has been decrypted), hence using mutable context. +/// CLI argument that found via the [`ChainContext`] and cached (as in case of +/// an encrypted keypair that has been decrypted), hence using mutable context. pub trait ArgFromMutContext: Sized { fn arg_from_mut_ctx( - ctx: &mut Context, + ctx: &mut ChainContext, raw: impl AsRef, ) -> Result; } impl ArgFromContext for Address { fn arg_from_ctx( - ctx: &Context, + ctx: &ChainContext, raw: impl AsRef, ) -> Result { struct Skip; @@ -404,14 +388,19 @@ impl ArgFromContext for Address { .ok_or(Skip) }) // Or it can be an alias that may be found in the wallet - .or_else(|_| ctx.wallet.find_address(raw).cloned().ok_or(Skip)) + .or_else(|_| { + ctx.wallet + .find_address(raw) + .map(|x| x.into_owned()) + .ok_or(Skip) + }) .map_err(|_| format!("Unknown address {raw}")) } } impl ArgFromMutContext for common::SecretKey { fn arg_from_mut_ctx( - ctx: &mut Context, + ctx: &mut ChainContext, raw: impl AsRef, ) -> Result { let raw = raw.as_ref(); @@ -427,7 +416,7 @@ impl ArgFromMutContext for common::SecretKey { impl ArgFromMutContext for common::PublicKey { fn arg_from_mut_ctx( - ctx: &mut Context, + ctx: &mut ChainContext, raw: impl AsRef, ) -> Result { let raw = raw.as_ref(); @@ -452,7 +441,7 @@ impl ArgFromMutContext for common::PublicKey { impl ArgFromMutContext for ExtendedSpendingKey { fn arg_from_mut_ctx( - ctx: &mut Context, + ctx: &mut ChainContext, raw: impl AsRef, ) -> Result { let raw = raw.as_ref(); @@ -468,7 +457,7 @@ impl ArgFromMutContext for ExtendedSpendingKey { impl ArgFromMutContext for ExtendedViewingKey { fn arg_from_mut_ctx( - ctx: &mut Context, + ctx: &mut ChainContext, raw: impl AsRef, ) -> Result { let raw = raw.as_ref(); @@ -485,7 +474,7 @@ impl ArgFromMutContext for ExtendedViewingKey { impl ArgFromContext for PaymentAddress { fn arg_from_ctx( - ctx: &Context, + ctx: &ChainContext, raw: impl AsRef, ) -> Result { let raw = raw.as_ref(); @@ -502,7 +491,7 @@ impl ArgFromContext for PaymentAddress { impl ArgFromMutContext for TransferSource { fn arg_from_mut_ctx( - ctx: &mut Context, + ctx: &mut ChainContext, raw: impl AsRef, ) -> Result { let raw = raw.as_ref(); @@ -518,7 +507,7 @@ impl ArgFromMutContext for TransferSource { impl ArgFromContext for TransferTarget { fn arg_from_ctx( - ctx: &Context, + ctx: &ChainContext, raw: impl AsRef, ) -> Result { let raw = raw.as_ref(); @@ -533,7 +522,7 @@ impl ArgFromContext for TransferTarget { impl ArgFromMutContext for BalanceOwner { fn arg_from_mut_ctx( - ctx: &mut Context, + ctx: &mut ChainContext, raw: impl AsRef, ) -> Result { let raw = raw.as_ref(); diff --git a/apps/src/lib/cli/wallet.rs b/apps/src/lib/cli/wallet.rs index 14b9ab9d9a..3b43e09b74 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -16,14 +16,16 @@ use namada_sdk::wallet::{ WalletStorage, }; use namada_sdk::{display, display_line, edisplay_line}; -use rand::RngCore; use rand_core::OsRng; use crate::cli; use crate::cli::api::CliApi; use crate::cli::args::CliToSdk; use crate::cli::{args, cmds, Context}; -use crate::wallet::{read_and_confirm_encryption_password, CliWalletUtils}; +use crate::client::utils::PRE_GENESIS_DIR; +use crate::wallet::{ + self, read_and_confirm_encryption_password, CliWalletUtils, +}; impl CliApi { pub fn handle_wallet_command( @@ -34,57 +36,69 @@ impl CliApi { match cmd { cmds::NamadaWallet::Key(sub) => match sub { cmds::WalletKey::Restore(cmds::KeyRestore(args)) => { - key_and_address_restore(&mut ctx.wallet, io, args) + key_and_address_restore( + &mut ctx.borrow_mut_chain_or_exit().wallet, + io, + args, + ) } cmds::WalletKey::Gen(cmds::KeyGen(args)) => { - key_and_address_gen(&mut ctx.wallet, io, &mut OsRng, args) + key_and_address_gen(ctx, io, args) } cmds::WalletKey::Find(cmds::KeyFind(args)) => { - key_find(&mut ctx.wallet, io, args) + key_find(ctx, io, args) } cmds::WalletKey::List(cmds::KeyList(args)) => { - key_list(&mut ctx.wallet, io, args) + key_list(ctx, io, args) } cmds::WalletKey::Export(cmds::Export(args)) => { - key_export(&mut ctx.wallet, io, args) + key_export(ctx, io, args) } }, cmds::NamadaWallet::Address(sub) => match sub { cmds::WalletAddress::Gen(cmds::AddressGen(args)) => { - key_and_address_gen(&mut ctx.wallet, io, &mut OsRng, args) + key_and_address_gen(ctx, io, args) } cmds::WalletAddress::Restore(cmds::AddressRestore(args)) => { - key_and_address_restore(&mut ctx.wallet, io, args) + key_and_address_restore( + &mut ctx.borrow_mut_chain_or_exit().wallet, + io, + args, + ) } cmds::WalletAddress::Find(cmds::AddressOrAliasFind(args)) => { - address_or_alias_find(&mut ctx.wallet, io, args) + address_or_alias_find(ctx, io, args) } - cmds::WalletAddress::List(cmds::AddressList) => { - address_list(&mut ctx.wallet, io) + cmds::WalletAddress::List(cmds::AddressList(args)) => { + address_list(ctx, io, args) } cmds::WalletAddress::Add(cmds::AddressAdd(args)) => { - address_add(&mut ctx.wallet, io, args) + address_add(ctx, io, args) } }, cmds::NamadaWallet::Masp(sub) => match sub { cmds::WalletMasp::GenSpendKey(cmds::MaspGenSpendKey(args)) => { - spending_key_gen(&mut ctx.wallet, io, args) + spending_key_gen(ctx, io, args) } cmds::WalletMasp::GenPayAddr(cmds::MaspGenPayAddr(args)) => { let args = args.to_sdk(&mut ctx); - payment_address_gen(&mut ctx.wallet, io, args) + payment_address_gen( + &mut ctx.borrow_mut_chain_or_exit().wallet, + io, + args, + ) } cmds::WalletMasp::AddAddrKey(cmds::MaspAddAddrKey(args)) => { - address_key_add(&mut ctx.wallet, io, args) - } - cmds::WalletMasp::ListPayAddrs(cmds::MaspListPayAddrs) => { - payment_addresses_list(&mut ctx.wallet, io) + address_key_add(ctx, io, args) } + cmds::WalletMasp::ListPayAddrs(cmds::MaspListPayAddrs( + args, + )) => payment_addresses_list(ctx, io, args), cmds::WalletMasp::ListKeys(cmds::MaspListKeys(args)) => { - spending_keys_list(&mut ctx.wallet, io, args) + spending_keys_list(ctx, io, args) } cmds::WalletMasp::FindAddrKey(cmds::MaspFindAddrKey(args)) => { - address_key_find(&mut ctx.wallet, io, args) + address_key_find(ctx, io, args) } }, } @@ -94,13 +108,15 @@ impl CliApi { /// Find shielded address or key fn address_key_find( - wallet: &mut Wallet, + ctx: Context, io: &impl Io, args::AddrKeyFind { alias, unsafe_show_secret, + is_pre_genesis, }: args::AddrKeyFind, ) { + let mut wallet = load_wallet(ctx, is_pre_genesis); let alias = alias.to_lowercase(); if let Ok(viewing_key) = wallet.find_viewing_key(&alias) { // Check if alias is a viewing key @@ -132,13 +148,15 @@ fn address_key_find( /// List spending keys. fn spending_keys_list( - wallet: &mut Wallet, + ctx: Context, io: &impl Io, args::MaspKeysList { decrypt, + is_pre_genesis, unsafe_show_secret, }: args::MaspKeysList, ) { + let wallet = load_wallet(ctx, is_pre_genesis); let known_view_keys = wallet.get_viewing_keys(); let known_spend_keys = wallet.get_spending_keys(); if known_view_keys.is_empty() { @@ -207,9 +225,11 @@ fn spending_keys_list( /// List payment addresses. fn payment_addresses_list( - wallet: &mut Wallet, + ctx: Context, io: &impl Io, + args::MaspListPayAddrs { is_pre_genesis }: args::MaspListPayAddrs, ) { + let wallet = load_wallet(ctx, is_pre_genesis); let known_addresses = wallet.get_payment_addrs(); if known_addresses.is_empty() { display_line!( @@ -229,14 +249,16 @@ fn payment_addresses_list( /// Generate a spending key. fn spending_key_gen( - wallet: &mut Wallet, + ctx: Context, io: &impl Io, args::MaspSpendKeyGen { alias, alias_force, + is_pre_genesis, unsafe_dont_encrypt, }: args::MaspSpendKeyGen, ) { + let mut wallet = load_wallet(ctx, is_pre_genesis); let alias = alias.to_lowercase(); let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); let (alias, _key) = wallet.gen_spending_key(alias, password, alias_force); @@ -257,6 +279,7 @@ fn payment_address_gen( alias_force, viewing_key, pin, + .. }: args::MaspPayAddrGen, ) { let alias = alias.to_lowercase(); @@ -285,16 +308,18 @@ fn payment_address_gen( /// Add a viewing key, spending key, or payment address to wallet. fn address_key_add( - wallet: &mut Wallet, + ctx: Context, io: &impl Io, args::MaspAddrKeyAdd { alias, alias_force, value, + is_pre_genesis, unsafe_dont_encrypt, }: args::MaspAddrKeyAdd, ) { let alias = alias.to_lowercase(); + let mut wallet = load_wallet(ctx, is_pre_genesis); let (alias, typ) = match value { MaspValue::FullViewingKey(viewing_key) => { let alias = wallet @@ -384,21 +409,24 @@ fn key_and_address_restore( /// Generate a new keypair and derive implicit address from it and store them in /// the wallet. -fn key_and_address_gen( - wallet: &mut Wallet>, +fn key_and_address_gen( + ctx: Context, io: &impl Io, - rng: &mut R, args::KeyAndAddressGen { scheme, alias, alias_force, + is_pre_genesis, unsafe_dont_encrypt, derivation_path, }: args::KeyAndAddressGen, ) { + let mut wallet = load_wallet(ctx, is_pre_genesis); let encryption_password = read_and_confirm_encryption_password(unsafe_dont_encrypt); - let derivation_path_and_mnemonic_rng = derivation_path.map(|p| (p, rng)); + let mut rng = OsRng; + let derivation_path_and_mnemonic_rng = + derivation_path.map(|p| (p, &mut rng)); let (alias, _key, _mnemonic) = wallet .gen_key( scheme, @@ -410,11 +438,11 @@ fn key_and_address_gen( ) .unwrap_or_else(|err| match err { GenRestoreKeyError::KeyStorageError => { - println!("No changes are persisted. Exiting."); + display_line!(io, "No changes are persisted. Exiting."); cli::safe_exit(0); } _ => { - eprintln!("{}", err); + edisplay_line!(io, "{}", err); cli::safe_exit(1); } }); @@ -430,15 +458,17 @@ fn key_and_address_gen( /// Find a keypair in the wallet store. fn key_find( - wallet: &mut Wallet, + ctx: Context, io: &impl Io, args::KeyFind { public_key, alias, value, + is_pre_genesis, unsafe_show_secret, }: args::KeyFind, ) { + let mut wallet = load_wallet(ctx, is_pre_genesis); let found_keypair = match public_key { Some(pk) => wallet.find_key_by_pk(&pk, None), None => { @@ -473,13 +503,15 @@ fn key_find( /// List all known keys. fn key_list( - wallet: &mut Wallet, + ctx: Context, io: &impl Io, args::KeyList { decrypt, + is_pre_genesis, unsafe_show_secret, }: args::KeyList, ) { + let wallet = load_wallet(ctx, is_pre_genesis); let known_keys = wallet.get_keys(); if known_keys.is_empty() { display_line!( @@ -538,10 +570,14 @@ fn key_list( /// Export a keypair to a file. fn key_export( - wallet: &mut Wallet, + ctx: Context, io: &impl Io, - args::KeyExport { alias }: args::KeyExport, + args::KeyExport { + alias, + is_pre_genesis, + }: args::KeyExport, ) { + let mut wallet = load_wallet(ctx, is_pre_genesis); wallet .find_key(alias.to_lowercase(), None) .map(|keypair| { @@ -560,9 +596,11 @@ fn key_export( /// List all known addresses. fn address_list( - wallet: &mut Wallet, + ctx: Context, io: &impl Io, + args::AddressList { is_pre_genesis }: args::AddressList, ) { + let wallet = load_wallet(ctx, is_pre_genesis); let known_addresses = wallet.get_addresses(); if known_addresses.is_empty() { display_line!( @@ -586,36 +624,40 @@ fn address_list( /// Find address (alias) by its alias (address). fn address_or_alias_find( - wallet: &mut Wallet, + ctx: Context, io: &impl Io, - args: args::AddressOrAliasFind, + args::AddressOrAliasFind { + alias, + address, + is_pre_genesis, + }: args::AddressOrAliasFind, ) { - if args.address.is_some() && args.alias.is_some() { + let wallet = load_wallet(ctx, is_pre_genesis); + if address.is_some() && alias.is_some() { panic!( "This should not be happening: clap should emit its own error \ message." ); - } else if args.alias.is_some() { - if let Some(address) = wallet.find_address(args.alias.as_ref().unwrap()) - { + } else if alias.is_some() { + if let Some(address) = wallet.find_address(alias.as_ref().unwrap()) { display_line!(io, "Found address {}", address.to_pretty_string()); } else { display_line!( io, "No address with alias {} found. Use the command `address \ list` to see all the known addresses.", - args.alias.unwrap().to_lowercase() + alias.unwrap().to_lowercase() ); } - } else if args.address.is_some() { - if let Some(alias) = wallet.find_alias(args.address.as_ref().unwrap()) { + } else if address.is_some() { + if let Some(alias) = wallet.find_alias(address.as_ref().unwrap()) { display_line!(io, "Found alias {}", alias); } else { display_line!( io, "No alias with address {} found. Use the command `address \ list` to see all the known addresses.", - args.address.unwrap() + address.unwrap() ); } } @@ -623,16 +665,18 @@ fn address_or_alias_find( /// Add an address to the wallet. fn address_add( - wallet: &mut Wallet, + ctx: Context, io: &impl Io, - args: args::AddressAdd, + args::AddressAdd { + alias, + alias_force, + address, + is_pre_genesis, + }: args::AddressAdd, ) { + let mut wallet = load_wallet(ctx, is_pre_genesis); if wallet - .add_address( - args.alias.clone().to_lowercase(), - args.address, - args.alias_force, - ) + .add_address(alias.to_lowercase(), address, alias_force) .is_none() { edisplay_line!(io, "Address not added"); @@ -644,6 +688,17 @@ fn address_add( display_line!( io, "Successfully added a key and an address with alias: \"{}\"", - args.alias.to_lowercase() + alias.to_lowercase() ); } + +/// Load wallet for chain when `ctx.chain.is_some()` or pre-genesis wallet when +/// `is_pre_genesis || ctx.chain.is_none()`. +fn load_wallet(ctx: Context, is_pre_genesis: bool) -> Wallet { + if is_pre_genesis || ctx.chain.is_none() { + let wallet_path = ctx.global_args.base_dir.join(PRE_GENESIS_DIR); + wallet::load_or_new(&wallet_path) + } else { + ctx.take_chain_or_exit().wallet + } +} diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index e6ddbff2e9..0231e3731d 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -410,9 +410,9 @@ pub async fn query_pinned_balance<'a>( .collect(); let _ = context.shielded_mut().await.load().await; // Print the token balances by payment address - let pinned_error = Err(Error::from(PinnedBalanceError::InvalidViewingKey)); for owner in owners { - let mut balance = pinned_error.clone(); + let mut balance = + Err(Error::from(PinnedBalanceError::InvalidViewingKey)); // Find the viewing key that can recognize payments the current payment // address for vk in &viewing_keys { diff --git a/apps/src/lib/client/types.rs b/apps/src/lib/client/types.rs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 16bf625c23..a38b8714ca 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -1,4 +1,3 @@ -use std::collections::HashMap; use std::env; use std::fs::{self, File, OpenOptions}; use std::io::Write; @@ -9,35 +8,32 @@ use borsh_ext::BorshSerializeExt; use flate2::read::GzDecoder; use flate2::write::GzEncoder; use flate2::Compression; -use namada::types::address; use namada::types::chain::ChainId; use namada::types::dec::Dec; use namada::types::key::*; +use namada::types::token; +use namada::types::uint::Uint; use namada::vm::validate_untrusted_wasm; -use namada_sdk::wallet::Wallet; +use namada_sdk::wallet::{alias, Wallet}; use prost::bytes::Bytes; -use rand::prelude::ThreadRng; -use rand::thread_rng; use serde_json::json; use sha2::{Digest, Sha256}; +use crate::cli::args; use crate::cli::context::ENV_VAR_WASM_DIR; -use crate::cli::{self, args, safe_exit}; -use crate::config::genesis::genesis_config::{ - self, GenesisConfig, HexString, ValidatorPreGenesisConfig, -}; use crate::config::global::GlobalConfig; -use crate::config::{self, get_default_namada_folder, Config, TendermintMode}; +use crate::config::{ + self, genesis, get_default_namada_folder, Config, TendermintMode, +}; use crate::facade::tendermint::node::Id as TendermintNodeId; use crate::facade::tendermint_config::net::Address as TendermintAddress; use crate::node::ledger::tendermint_node; -use crate::wallet::{ - pre_genesis, read_and_confirm_encryption_password, CliWalletUtils, -}; +use crate::wallet::{pre_genesis, CliWalletUtils}; use crate::wasm_loader; pub const NET_ACCOUNTS_DIR: &str = "setup"; pub const NET_OTHER_ACCOUNTS_DIR: &str = "other"; +pub const ENV_VAR_NETWORK_CONFIGS_DIR: &str = "NAMADA_NETWORK_CONFIGS_DIR"; /// Github URL prefix of released Namada network configs pub const ENV_VAR_NETWORK_CONFIGS_SERVER: &str = "NAMADA_NETWORK_CONFIGS_SERVER"; @@ -56,6 +52,7 @@ pub async fn join_network( genesis_validator, pre_genesis_path, dont_prefetch_wasm, + allow_duplicate_ip, }: args::JoinNetwork, ) { use tokio::fs; @@ -74,7 +71,7 @@ pub async fn join_network( .is_ok() { eprintln!("The chain directory for {} already exists.", chain_id); - cli::safe_exit(1); + safe_exit(1); } } let base_dir_full = fs::canonicalize(&base_dir).await.unwrap(); @@ -109,12 +106,12 @@ pub async fn join_network( let validator_alias_and_pre_genesis_wallet = validator_alias_and_dir.map(|(validator_alias, pre_genesis_dir)| { ( - validator_alias, + alias::Alias::from(validator_alias), pre_genesis::load(&pre_genesis_dir).unwrap_or_else(|err| { eprintln!( "Error loading validator pre-genesis wallet {err}", ); - cli::safe_exit(1) + safe_exit(1) }), ) }); @@ -129,24 +126,37 @@ pub async fn join_network( }); let release_filename = format!("{}.tar.gz", chain_id); - let release_url = format!( - "{}/{}", - network_configs_url_prefix(&chain_id), - release_filename - ); + let net_config = if let Some(configs_dir) = network_configs_dir() { + fs::read(PathBuf::from(&configs_dir).join(release_filename)) + .await + .unwrap_or_else(|err| { + panic!( + "Network config not found or couldn't be read from dir \ + \"{configs_dir}\" set by an env var \ + {ENV_VAR_NETWORK_CONFIGS_DIR}. Error: {err}." + ) + }) + } else { + let release_url = format!( + "{}/{}", + network_configs_url_prefix(&chain_id), + release_filename + ); - // Read or download the release archive - println!("Downloading config release from {} ...", release_url); - let release = match download_file(release_url).await { - Ok(contents) => contents, - Err(error) => { - eprintln!("Error downloading release: {}", error); - cli::safe_exit(1); - } + // Read or download the release archive + println!("Downloading config release from {} ...", release_url); + let release: Bytes = match download_file(release_url).await { + Ok(contents) => contents, + Err(error) => { + eprintln!("Error downloading release: {}", error); + safe_exit(1); + } + }; + release.to_vec() }; // Decode and unpack the archive - let decoder = GzDecoder::new(&release[..]); + let decoder = GzDecoder::new(&net_config[..]); let mut archive = tar::Archive::new(decoder); // If the base-dir is non-default, unpack the archive into a temp dir inside @@ -190,16 +200,6 @@ pub async fn join_network( .await .unwrap(); - // Move the genesis file - fs::rename( - unpack_dir - .join(config::DEFAULT_BASE_DIR) - .join(format!("{}.toml", chain_id.as_str())), - base_dir_full.join(format!("{}.toml", chain_id.as_str())), - ) - .await - .unwrap(); - // Move the global config fs::rename( unpack_dir @@ -216,6 +216,84 @@ pub async fn join_network( .unwrap(); } + // Read the genesis files + let genesis = genesis::chain::Finalized::read_toml_files(&chain_dir) + .unwrap_or_else(|err| { + eprintln!( + "Failed to read genesis TOML files from {} with {err}.", + chain_dir.to_string_lossy() + ); + safe_exit(1) + }); + + // Try to find validator data when using a pre-genesis validator + let validator_alias = validator_alias_and_pre_genesis_wallet + .as_ref() + .map(|(alias, _wallet)| alias.clone()); + let validator_keys = validator_alias_and_pre_genesis_wallet.as_ref().map( + |(_alias, wallet)| { + let tendermint_node_key: common::SecretKey = wallet + .tendermint_node_key + .try_to_sk() + .unwrap_or_else(|_err| { + eprintln!( + "Tendermint node key must be common (need to change?)" + ); + safe_exit(1) + }); + (tendermint_node_key, wallet.consensus_key.clone()) + }, + ); + let node_mode = if validator_alias.is_some() { + TendermintMode::Validator + } else { + TendermintMode::Full + }; + + // Derive config from genesis + let config = genesis.derive_config( + &chain_dir, + node_mode, + validator_alias, + allow_duplicate_ip, + ); + + // Try to load pre-genesis wallet, if any + let pre_genesis_wallet_path = base_dir.join(PRE_GENESIS_DIR); + let pre_genesis_wallet = crate::wallet::load(&pre_genesis_wallet_path); + // Derive wallet from genesis + let wallet = genesis.derive_wallet( + &chain_dir, + pre_genesis_wallet, + validator_alias_and_pre_genesis_wallet, + ); + + // Save the config and the wallet + config.write(&base_dir, &chain_id, true).unwrap(); + crate::wallet::save(&wallet).unwrap(); + + // Setup the node for a genesis validator, if used + if let Some((tendermint_node_key, consensus_key)) = validator_keys { + println!( + "Setting up validator keys in CometBFT. Consensus key: {}.", + consensus_key.to_public() + ); + let tm_home_dir = chain_dir.join(config::COMETBFT_DIR); + // Write consensus key to tendermint home + tendermint_node::write_validator_key(&tm_home_dir, &consensus_key); + + // Write tendermint node key + write_tendermint_node_key(&tm_home_dir, tendermint_node_key); + + // Pre-initialize tendermint validator state + tendermint_node::write_validator_state(&tm_home_dir); + } else { + println!( + "No validator keys are being used. Make sure you didn't forget to \ + specify `--genesis-validator`?" + ); + } + // Move wasm-dir and update config if it's non-default if let Some(wasm_dir) = wasm_dir.as_ref() { if wasm_dir.to_string_lossy() != config::DEFAULT_WASM_DIR { @@ -242,102 +320,6 @@ pub async fn join_network( } } - // Setup the node for a genesis validator, if used - if let Some((validator_alias, pre_genesis_wallet)) = - validator_alias_and_pre_genesis_wallet - { - let tendermint_node_key: common::SecretKey = pre_genesis_wallet - .tendermint_node_key - .try_to_sk() - .unwrap_or_else(|_err| { - eprintln!( - "Tendermint node key must be common (need to change?)" - ); - cli::safe_exit(1) - }); - - let genesis_file_path = - base_dir.join(format!("{}.toml", chain_id.as_str())); - let genesis_config = - genesis_config::open_genesis_config(genesis_file_path).unwrap(); - - if !is_valid_validator_for_current_chain( - &tendermint_node_key.ref_to(), - &genesis_config, - ) { - eprintln!( - "The current validator is not valid for chain {}.", - chain_id.as_str() - ); - safe_exit(1) - } - - let mut wallet = - crate::wallet::load_or_new_from_genesis(&chain_dir, genesis_config); - - let address = wallet - .find_address(&validator_alias) - .unwrap_or_else(|| { - eprintln!( - "Unable to find validator address for alias \ - {validator_alias}" - ); - cli::safe_exit(1) - }) - .clone(); - - let tm_home_dir = chain_dir.join("cometbft"); - - // Write consensus key to tendermint home - tendermint_node::write_validator_key( - &tm_home_dir, - &pre_genesis_wallet.consensus_key, - ); - - // Derive the node ID from the node key - let node_id = id_from_pk(&tendermint_node_key.ref_to()); - // Write tendermint node key - write_tendermint_node_key(&tm_home_dir, tendermint_node_key); - - // Pre-initialize tendermint validator state - tendermint_node::write_validator_state(&tm_home_dir); - - // Extend the current wallet from the pre-genesis wallet. - // This takes the validator keys to be usable in future commands (e.g. - // to sign a tx from validator account using the account key). - wallet.extend_from_pre_genesis_validator( - address, - validator_alias.into(), - pre_genesis_wallet, - ); - - crate::wallet::save(&wallet).unwrap(); - - // Update the config from the default non-validator settings to - // validator settings - let base_dir = base_dir.clone(); - let chain_id = chain_id.clone(); - tokio::task::spawn_blocking(move || { - let mut config = Config::load(&base_dir, &chain_id, None); - config.ledger.shell.tendermint_mode = TendermintMode::Validator; - - // Remove self from persistent peers - config.ledger.cometbft.p2p.persistent_peers.retain(|peer| { - if let TendermintAddress::Tcp { - peer_id: Some(peer_id), - .. - } = peer - { - node_id != *peer_id - } else { - true - } - }); - config.write(&base_dir, &chain_id, true).unwrap(); - }) - .await - .unwrap(); - } if !dont_prefetch_wasm { fetch_wasms_aux(&base_dir, &chain_id).await; } @@ -369,7 +351,7 @@ pub fn validate_wasm(args::ValidateWasm { code_path }: args::ValidateWasm) { Ok(()) => println!("Wasm code is valid"), Err(e) => { eprintln!("Wasm code is invalid: {e}"); - cli::safe_exit(1) + safe_exit(1) } } } @@ -405,345 +387,115 @@ pub fn id_from_pk(pk: &common::PublicKey) -> TendermintNodeId { pub fn init_network( global_args: args::Global, args::InitNetwork { - genesis_path, + templates_path, wasm_checksums_path, chain_id_prefix, - unsafe_dont_encrypt, + genesis_time, consensus_timeout_commit, - localhost, - allow_duplicate_ip, dont_archive, archive_dir, }: args::InitNetwork, ) { - let mut config = genesis_config::open_genesis_config(genesis_path).unwrap(); - - // Update the WASM checksums - let checksums = - wasm_loader::Checksums::read_checksums_file(&wasm_checksums_path); - config.wasm.iter_mut().for_each(|(name, config)| { - // Find the sha256 from checksums.json - let name = format!("{}.wasm", name); - // Full name in format `{name}.{sha256}.wasm` - let full_name = checksums.0.get(&name).unwrap(); - let hash = full_name - .split_once('.') - .unwrap() - .1 - .split_once('.') - .unwrap() - .0; - config.sha256 = Some(genesis_config::HexString(hash.to_owned())); - }); - - // The `temp_chain_id` gets renamed after we have chain ID. - let temp_chain_id = chain_id_prefix.temp_chain_id(); - let temp_dir = global_args.base_dir.join(temp_chain_id.as_str()); - // The `temp_chain_id` gets renamed after we have chain ID - let accounts_dir = temp_dir.join(NET_ACCOUNTS_DIR); - // Base dir used in account sub-directories - let accounts_temp_dir = - PathBuf::from(config::DEFAULT_BASE_DIR).join(temp_chain_id.as_str()); - - let mut rng: ThreadRng = thread_rng(); - - // Accumulator of validators' Tendermint P2P addresses - let mut persistent_peers: Vec = - Vec::with_capacity(config.validator.len()); - - // Iterate over each validator, generating keys and addresses - config.validator.iter_mut().for_each(|(name, config)| { - let validator_dir = accounts_dir.join(name); - - let chain_dir = validator_dir.join(&accounts_temp_dir); - let tm_home_dir = chain_dir.join("cometbft"); - - // Find or generate tendermint node key - let node_pk = try_parse_public_key( - format!("validator {name} Tendermint node key"), - &config.tendermint_node_key, - ) - .unwrap_or_else(|| { - // Generate a node key with ed25519 as default - let node_sk = common::SecretKey::Ed25519( - ed25519::SigScheme::generate(&mut rng), - ); - - let node_pk = write_tendermint_node_key(&tm_home_dir, node_sk); - - tendermint_node::write_validator_state(&tm_home_dir); - - node_pk - }); - - // Derive the node ID from the node key - let node_id: TendermintNodeId = id_from_pk(&node_pk); - - // Build the list of persistent peers from the validators' node IDs - let peer = TendermintAddress::from_str(&format!( - "{}@{}", - node_id, - config.net_address.as_ref().unwrap(), - )) - .expect("Validator address must be valid"); - persistent_peers.push(peer); - - // Generate account and reward addresses - let address = address::gen_established_address("validator account"); - config.address = Some(address.to_string()); - - // Generate the consensus, account and reward keys, unless they're - // pre-defined. Do not use mnemonic code / HD derivation path. - let mut wallet = crate::wallet::load_or_new(&chain_dir); - - let consensus_pk = try_parse_public_key( - format!("validator {name} consensus key"), - &config.consensus_public_key, - ) - .unwrap_or_else(|| { - let alias = format!("{}-consensus-key", name); - println!("Generating validator {} consensus key...", name); - let password = - read_and_confirm_encryption_password(unsafe_dont_encrypt); - let (_alias, keypair, _mnemonic) = wallet - .gen_key( - SchemeType::Ed25519, - Some(alias), - true, - None, - password, - None, - ) - .expect("Key generation should not fail."); - - // Write consensus key for Tendermint - tendermint_node::write_validator_key(&tm_home_dir, &keypair); - - keypair.ref_to() - }); - - let account_pk = try_parse_public_key( - format!("validator {name} account key"), - &config.account_public_key, - ) - .unwrap_or_else(|| { - let alias = format!("{}-account-key", name); - println!("Generating validator {} account key...", name); - let password = - read_and_confirm_encryption_password(unsafe_dont_encrypt); - let (_alias, keypair, _mnemonic) = wallet - .gen_key( - SchemeType::Ed25519, - Some(alias), - true, - None, - password, - None, - ) - .expect("Key generation should not fail."); - keypair.ref_to() - }); - - let protocol_pk = try_parse_public_key( - format!("validator {name} protocol key"), - &config.protocol_public_key, - ) + // Load and validate the templates + let templates = genesis::templates::load_and_validate(&templates_path) .unwrap_or_else(|| { - let alias = format!("{}-protocol-key", name); - println!("Generating validator {} protocol signing key...", name); - let password = - read_and_confirm_encryption_password(unsafe_dont_encrypt); - let (_alias, keypair, _mnemonic) = wallet - .gen_key( - SchemeType::Ed25519, - Some(alias), - true, - None, - password, - None, - ) - .expect("Key generation should not fail."); - keypair.ref_to() + eprintln!("Invalid templates, aborting."); + safe_exit(1) }); - let eth_hot_pk = try_parse_public_key( - format!("validator {name} eth hot key"), - &config.eth_hot_key, - ) - .unwrap_or_else(|| { - let alias = format!("{}-eth-hot-key", name); - println!("Generating validator {} eth hot key...", name); - let password = - read_and_confirm_encryption_password(unsafe_dont_encrypt); - let (_alias, keypair, _mnemonic) = wallet - .gen_key( - SchemeType::Secp256k1, - Some(alias), - true, - None, - password, - None, - ) - .expect("Key generation should not fail."); - keypair.ref_to() - }); + // In addition to standard templates validation, check that there is at + // least one validator account. + if !templates.transactions.has_at_least_one_validator() { + eprintln!("No validator genesis transaction found, aborting."); + safe_exit(1) + } - let eth_cold_pk = try_parse_public_key( - format!("validator {name} eth cold key"), - &config.eth_cold_key, + // Also check that at least one validator account has positive voting power. + let tm_votes_per_token = templates.parameters.pos_params.tm_votes_per_token; + if !templates + .transactions + .has_validator_with_positive_voting_power(tm_votes_per_token) + { + let min_stake = token::Amount::from_uint( + if tm_votes_per_token > Dec::from(1) { + Uint::one() + } else { + (Dec::from(1) / tm_votes_per_token).ceil().abs() + }, + token::NATIVE_MAX_DECIMAL_PLACES, ) - .unwrap_or_else(|| { - let alias = format!("{}-eth-cold-key", name); - println!("Generating validator {} eth cold key...", name); - let password = - read_and_confirm_encryption_password(unsafe_dont_encrypt); - let (_alias, keypair, _mnemonic) = wallet - .gen_key( - SchemeType::Secp256k1, - Some(alias), - true, - None, - password, - None, - ) - .expect("Key generation should not fail."); - keypair.ref_to() - }); - - let dkg_pk = &config - .dkg_public_key - .as_ref() - .map(|key| { - key.to_dkg_public_key().unwrap_or_else(|err| { - let label = format!("validator {name} DKG key"); - eprintln!("Invalid {label} key: {}", err); - cli::safe_exit(1) - }) - }) - .unwrap_or_else(|| { - println!( - "Generating validator {} DKG session keypair...", - name - ); - - let validator_keys = crate::wallet::gen_validator_keys( - &mut wallet, - Some(eth_hot_pk.clone()), - Some(protocol_pk.clone()), - SchemeType::Ed25519, - ) - .expect("Generating new validator keys should not fail"); - let pk = validator_keys.dkg_keypair.as_ref().unwrap().public(); - wallet.add_validator_data(address.clone(), validator_keys); - pk - }); - - // Add the validator public keys to genesis config - config.consensus_public_key = - Some(genesis_config::HexString(consensus_pk.to_string())); - config.account_public_key = - Some(genesis_config::HexString(account_pk.to_string())); - config.eth_cold_key = - Some(genesis_config::HexString(eth_cold_pk.to_string())); - config.eth_hot_key = - Some(genesis_config::HexString(eth_hot_pk.to_string())); - - config.protocol_public_key = - Some(genesis_config::HexString(protocol_pk.to_string())); - config.dkg_public_key = - Some(genesis_config::HexString(dkg_pk.to_string())); - - // Write keypairs to wallet - wallet.add_address(name.clone(), address, true); - - crate::wallet::save(&wallet).unwrap(); - }); - - // Create a wallet for all accounts other than validators. - // Do not use mnemonic code / HD derivation path. - let mut wallet = - crate::wallet::load_or_new(&accounts_dir.join(NET_OTHER_ACCOUNTS_DIR)); - if let Some(established) = &mut config.established { - established.iter_mut().for_each(|(name, config)| { - init_established_account( - name, - &mut wallet, - config, - unsafe_dont_encrypt, - ); - }) + .unwrap(); + eprintln!( + "No validator with positive voting power, aborting. The minimum \ + staked tokens amount required to run the network is {}, because \ + there are {tm_votes_per_token} votes per NAMNAM tokens.", + min_stake.to_string_native(), + ); + safe_exit(1) } - config.token.iter_mut().for_each(|(_name, config)| { - if config.address.is_none() { - let address = address::gen_established_address("token"); - config.address = Some(address.to_string()); - } - if config.vp.is_none() { - config.vp = Some("vp_token".to_string()); - } - }); + // Finalize the genesis config to derive the chain ID + let genesis = genesis::chain::finalize( + templates, + chain_id_prefix, + genesis_time, + consensus_timeout_commit, + ); + let chain_id = &genesis.metadata.chain_id; + let chain_dir = global_args.base_dir.join(chain_id.as_str()); - if let Some(implicit) = &mut config.implicit { - implicit.iter_mut().for_each(|(name, config)| { - if config.public_key.is_none() { - println!( - "Generating implicit account {} key and address ...", - name - ); - let password = - read_and_confirm_encryption_password(unsafe_dont_encrypt); - let (_alias, keypair, _mnemonic) = wallet - .gen_key( - SchemeType::Ed25519, - Some(name.clone()), - true, - None, - password, - None, - ) - .expect("Key generation should not fail."); - let public_key = - genesis_config::HexString(keypair.ref_to().to_string()); - config.public_key = Some(public_key); + // Check that chain dir is empty + if chain_dir.exists() && chain_dir.read_dir().unwrap().next().is_some() { + println!( + "The target chain directory {} already exists and is not empty.", + chain_dir.to_string_lossy() + ); + loop { + let mut buffer = String::new(); + print!( + "Do you want to override the chain directory? Will exit \ + otherwise. [y/N]: " + ); + std::io::stdout().flush().unwrap(); + match std::io::stdin().read_line(&mut buffer) { + Ok(size) if size > 0 => { + // Isolate the single character representing the choice + let byte = buffer.chars().next().unwrap(); + buffer.clear(); + match byte { + 'y' | 'Y' => { + fs::remove_dir_all(&chain_dir).unwrap(); + break; + } + 'n' | 'N' => { + println!("Exiting."); + safe_exit(1) + } + // Input is senseless fall through to repeat prompt + _ => { + println!("Unrecognized input."); + } + }; + } + _ => {} } - }) + } } + fs::create_dir_all(&chain_dir).unwrap(); - // Make a copy of genesis config without validator net addresses to - // `write_genesis_config`. Keep the original, because we still need the - // addresses to configure validators. - let mut config_clean = config.clone(); - config_clean - .validator - .iter_mut() - .for_each(|(_name, config)| { - config.net_address = None; - }); - - // Generate the chain ID first - let genesis = genesis_config::load_genesis_config(config_clean.clone()); - let genesis_bytes = genesis.serialize_to_vec(); - let chain_id = ChainId::from_genesis(chain_id_prefix, genesis_bytes); - let chain_dir = global_args.base_dir.join(chain_id.as_str()); - let genesis_path = global_args - .base_dir - .join(format!("{}.toml", chain_id.as_str())); - - // Write the genesis file - genesis_config::write_genesis_config(&config_clean, &genesis_path); - - // Add genesis addresses and save the wallet with other account keys - crate::wallet::add_genesis_addresses(&mut wallet, config_clean.clone()); - crate::wallet::save(&wallet).unwrap(); + // Write the finalized genesis config to the chain dir + genesis.write_toml_files(&chain_dir).unwrap_or_else(|err| { + eprintln!( + "Failed to write finalized genesis TOML files to {} with {err}.", + chain_dir.to_string_lossy() + ); + safe_exit(1) + }); // Write the global config setting the default chain ID let global_config = GlobalConfig::new(chain_id.clone()); global_config.write(&global_args.base_dir).unwrap(); - // Rename the generate chain config dir from `temp_chain_id` to `chain_id` - fs::rename(&temp_dir, &chain_dir).unwrap(); - // Copy the WASM checksums let wasm_dir_full = chain_dir.join(config::DEFAULT_WASM_DIR); fs::create_dir_all(&wasm_dir_full).unwrap(); @@ -753,149 +505,19 @@ pub fn init_network( ) .unwrap(); - config.validator.iter().for_each(|(name, _config)| { - let validator_dir = global_args - .base_dir - .join(chain_id.as_str()) - .join(NET_ACCOUNTS_DIR) - .join(name) - .join(config::DEFAULT_BASE_DIR); - let temp_validator_chain_dir = - validator_dir.join(temp_chain_id.as_str()); - let validator_chain_dir = validator_dir.join(chain_id.as_str()); - // Rename the generated directories for validators from `temp_chain_id` - // to `chain_id` - std::fs::rename(temp_validator_chain_dir, &validator_chain_dir) - .unwrap(); - - // Copy the WASM checksums - let wasm_dir_full = validator_chain_dir.join(config::DEFAULT_WASM_DIR); - fs::create_dir_all(&wasm_dir_full).unwrap(); - fs::copy( - &wasm_checksums_path, - wasm_dir_full.join(config::DEFAULT_WASM_CHECKSUMS_FILE), - ) - .unwrap(); - - // Write the genesis and global config into validator sub-dirs - genesis_config::write_genesis_config( - &config, - validator_dir.join(format!("{}.toml", chain_id.as_str())), - ); - global_config.write(validator_dir).unwrap(); - // Add genesis addresses to the validator's wallet - let mut wallet = crate::wallet::load_or_new(&validator_chain_dir); - crate::wallet::add_genesis_addresses(&mut wallet, config_clean.clone()); - crate::wallet::save(&wallet).unwrap(); - }); - - // Generate the validators' ledger config - config.validator.iter_mut().enumerate().for_each( - |(ix, (name, validator_config))| { - let accounts_dir = chain_dir.join(NET_ACCOUNTS_DIR); - let validator_dir = - accounts_dir.join(name).join(config::DEFAULT_BASE_DIR); - let mut config = Config::load( - &validator_dir, - &chain_id, - Some(TendermintMode::Validator), - ); - - // Configure the ledger - config.ledger.genesis_time = genesis.genesis_time.into(); - // In `config::Ledger`'s `base_dir`, `chain_id` and `tendermint`, - // the paths are prefixed with `validator_dir` given in the first - // parameter. We need to remove this prefix, because - // these sub-directories will be moved to validators' root - // directories. - config.ledger.shell.base_dir = config::DEFAULT_BASE_DIR.into(); - // Add a ledger P2P persistent peers - config.ledger.cometbft.p2p.persistent_peers = persistent_peers - .iter() - .enumerate() - .filter_map(|(index, peer)| - // we do not add the validator in its own persistent peer list - if index != ix { - Some(peer.to_owned()) - } else { - None - }) - .collect(); - - config.ledger.cometbft.consensus.timeout_commit = - consensus_timeout_commit; - config.ledger.cometbft.p2p.allow_duplicate_ip = allow_duplicate_ip; - config.ledger.cometbft.p2p.addr_book_strict = !localhost; - // Clear the net address from the config and use it to set ports - let net_address = validator_config.net_address.take().unwrap(); - let split: Vec<&str> = net_address.split(':').collect(); - let first_port = split[1].parse::().unwrap(); - if localhost { - config.ledger.cometbft.p2p.laddr = TendermintAddress::from_str( - &format!("127.0.0.1:{}", first_port), - ) - .unwrap(); - } else { - config.ledger.cometbft.p2p.laddr = TendermintAddress::from_str( - &format!("0.0.0.0:{}", first_port), - ) - .unwrap(); - } - if localhost { - config.ledger.cometbft.rpc.laddr = TendermintAddress::from_str( - &format!("127.0.0.1:{}", first_port + 1), - ) - .unwrap(); - } else { - config.ledger.cometbft.rpc.laddr = TendermintAddress::from_str( - &format!("0.0.0.0:{}", first_port + 1), - ) - .unwrap(); - } - if localhost { - config.ledger.cometbft.proxy_app = TendermintAddress::from_str( - &format!("127.0.0.1:{}", first_port + 2), - ) - .unwrap(); - } else { - config.ledger.cometbft.proxy_app = TendermintAddress::from_str( - &format!("0.0.0.0:{}", first_port + 2), - ) - .unwrap(); - } - config.write(&validator_dir, &chain_id, true).unwrap(); - }, - ); - - // Update the ledger config persistent peers and save it - let mut config = Config::load(&global_args.base_dir, &chain_id, None); - config.ledger.cometbft.p2p.persistent_peers = persistent_peers; - config.ledger.cometbft.consensus.timeout_commit = consensus_timeout_commit; - config.ledger.cometbft.p2p.allow_duplicate_ip = allow_duplicate_ip; - // Open P2P address - if !localhost { - config.ledger.cometbft.p2p.laddr = - TendermintAddress::from_str("0.0.0.0:26656").unwrap(); - } - config.ledger.cometbft.p2p.addr_book_strict = !localhost; - config.ledger.genesis_time = genesis.genesis_time.into(); - config - .write(&global_args.base_dir, &chain_id, true) - .unwrap(); - println!("Derived chain ID: {}", chain_id); - println!( - "Genesis file generated at {}", - genesis_path.to_string_lossy() - ); + println!("Genesis files stored at {}", chain_dir.to_string_lossy()); // Create a release tarball for anoma-network-config if !dont_archive { + // TODO: remove the `config::DEFAULT_BASE_DIR` and instead just archive + // the chain dir let mut release = tar::Builder::new(Vec::new()); - let release_genesis_path = PathBuf::from(config::DEFAULT_BASE_DIR) - .join(format!("{}.toml", chain_id.as_str())); release - .append_path_with_name(genesis_path, release_genesis_path) + .append_dir_all( + PathBuf::from(config::DEFAULT_BASE_DIR).join(chain_id.as_str()), + &chain_dir, + ) .unwrap(); let global_config_path = GlobalConfig::file_path(&global_args.base_dir); let release_global_config_path = @@ -906,13 +528,6 @@ pub fn init_network( release_global_config_path, ) .unwrap(); - let chain_config_path = - Config::file_path(&global_args.base_dir, &chain_id); - let release_chain_config_path = - Config::file_path(config::DEFAULT_BASE_DIR, &chain_id); - release - .append_path_with_name(chain_config_path, release_chain_config_path) - .unwrap(); let release_wasm_checksums_path = PathBuf::from(config::DEFAULT_BASE_DIR) .join(chain_id.as_str()) @@ -939,39 +554,20 @@ pub fn init_network( release_file.to_string_lossy() ); } -} -fn init_established_account( - name: impl AsRef, - wallet: &mut Wallet, - config: &mut genesis_config::EstablishedAccountConfig, - unsafe_dont_encrypt: bool, -) { - if config.address.is_none() { - let address = address::gen_established_address("established"); - config.address = Some(address.to_string()); - wallet.add_address(&name, address, true); - } - if config.public_key.is_none() { - println!("Generating established account {} key...", name.as_ref()); - let password = - read_and_confirm_encryption_password(unsafe_dont_encrypt); - let (_alias, keypair, _mnemonic) = wallet - .gen_key( - SchemeType::Ed25519, - Some(format!("{}-key", name.as_ref())), - true, - None, - password, - None, // do not use mnemonic code / HD derivation path - ) - .expect("Key generation should not fail."); - let public_key = - genesis_config::HexString(keypair.ref_to().to_string()); - config.public_key = Some(public_key); - } - if config.vp.is_none() { - config.vp = Some("vp_user".to_string()); + // After the archive is created, try to copy the built WASM, if they're + // present with the checksums. This is used for local network setup, so + // that we can use a local WASM build. + let checksums = wasm_loader::Checksums::read_checksums(&wasm_dir_full); + for (_, full_name) in checksums.0 { + // try to copy built file from the Namada WASM root dir + let file = std::env::current_dir() + .unwrap() + .join(crate::config::DEFAULT_WASM_DIR) + .join(&full_name); + if file.exists() { + fs::copy(file, wasm_dir_full.join(&full_name)).unwrap(); + } } } @@ -996,34 +592,50 @@ pub fn default_base_dir( } /// Initialize genesis validator's address, consensus key and validator account -/// key and use it in the ledger's node. +/// key into a special "pre-genesis" wallet. pub fn init_genesis_validator( global_args: args::Global, args::InitGenesisValidator { + source, alias, commission_rate, max_commission_rate_change, net_address, unsafe_dont_encrypt, key_scheme, + transfer_from_source_amount, + self_bond_amount, }: args::InitGenesisValidator, ) { + let (mut source_wallet, wallet_file) = + load_pre_genesis_wallet_or_exit(&global_args.base_dir); + + let source_key = + source_wallet.find_key(&source, None).unwrap_or_else(|err| { + eprintln!( + "Couldn't find key for source \"{source}\" in the pre-genesis \ + wallet {}. Failed with {err}.", + wallet_file.to_string_lossy() + ); + safe_exit(1) + }); + // Validate the commission rate data if commission_rate > Dec::one() { eprintln!("The validator commission rate must not exceed 1.0 or 100%"); - cli::safe_exit(1) + safe_exit(1) } if max_commission_rate_change > Dec::one() { eprintln!( "The validator maximum change in commission rate per epoch must \ not exceed 1.0 or 100%" ); - cli::safe_exit(1) + safe_exit(1) } let pre_genesis_dir = validator_pre_genesis_dir(&global_args.base_dir, &alias); println!("Generating validator keys..."); - let pre_genesis = pre_genesis::gen_and_store( + let validator_wallet = pre_genesis::gen_and_store( key_scheme, unsafe_dont_encrypt, &pre_genesis_dir, @@ -1033,78 +645,67 @@ pub fn init_genesis_validator( "Unable to generate the validator pre-genesis wallet: {}", err ); - cli::safe_exit(1) + safe_exit(1) }); println!( "The validator's keys were stored in the wallet at {}", pre_genesis::validator_file_name(&pre_genesis_dir).to_string_lossy() ); - let validator_config = ValidatorPreGenesisConfig { - validator: HashMap::from_iter([( - alias, - genesis_config::ValidatorConfig { - consensus_public_key: Some(HexString( - pre_genesis.consensus_key.ref_to().to_string(), - )), - eth_cold_key: Some(HexString( - pre_genesis.eth_cold_key.ref_to().to_string(), - )), - eth_hot_key: Some(HexString( - pre_genesis.eth_hot_key.ref_to().to_string(), - )), - account_public_key: Some(HexString( - pre_genesis.account_key.ref_to().to_string(), - )), - protocol_public_key: Some(HexString( - pre_genesis - .store - .validator_keys - .protocol_keypair - .ref_to() - .to_string(), - )), - dkg_public_key: Some(HexString( - pre_genesis - .store - .validator_keys - .dkg_keypair - .as_ref() - .unwrap() - .public() - .to_string(), - )), - commission_rate: Some(commission_rate), - max_commission_rate_change: Some(max_commission_rate_change), - tendermint_node_key: Some(HexString( - pre_genesis.tendermint_node_key.ref_to().to_string(), - )), - net_address: Some(net_address), - ..Default::default() - }, - )]), - }; - let genesis_part = toml::to_string(&validator_config).unwrap(); - println!("Your public partial pre-genesis TOML configuration:"); + let transactions = genesis::transactions::init_validator( + genesis::transactions::GenesisValidatorData { + source_key, + alias: alias::Alias::from(alias), + commission_rate, + max_commission_rate_change, + net_address, + transfer_from_source_amount, + self_bond_amount, + }, + &mut source_wallet, + &validator_wallet, + ); + + let genesis_part = toml::to_string(&transactions).unwrap(); + println!("Your public signed pre-genesis transactions TOML:"); println!(); println!("{genesis_part}"); - let file_name = validator_pre_genesis_file(&pre_genesis_dir); + let file_name = validator_pre_genesis_txs_file(&pre_genesis_dir); fs::write(&file_name, genesis_part).unwrap_or_else(|err| { eprintln!( - "Couldn't write partial pre-genesis file to {}. Failed with: {}", + "Couldn't write pre-genesis transactions file to {}. Failed with: \ + {}", file_name.to_string_lossy(), err ); - cli::safe_exit(1) + safe_exit(1) }); println!(); println!( - "Pre-genesis TOML written to {}", + "Pre-genesis transactions TOML written to {}", file_name.to_string_lossy() ); } +/// Try to load a pre-genesis wallet or terminate if it cannot be found. +pub fn load_pre_genesis_wallet_or_exit( + base_dir: &Path, +) -> (Wallet, PathBuf) { + let pre_genesis_dir = base_dir.join(PRE_GENESIS_DIR); + let wallet_file = crate::wallet::wallet_file(&pre_genesis_dir); + ( + crate::wallet::load(&pre_genesis_dir).unwrap_or_else(|| { + eprintln!( + "No pre-genesis wallet found at {}.", + wallet_file.to_string_lossy() + ); + safe_exit(1) + }), + wallet_file, + ) +} + async fn download_file(url: impl AsRef) -> reqwest::Result { let url = url.as_ref(); let response = reqwest::get(url).await?; @@ -1113,25 +714,16 @@ async fn download_file(url: impl AsRef) -> reqwest::Result { Ok(contents) } -fn try_parse_public_key( - label: impl AsRef, - value: &Option, -) -> Option { - let label = label.as_ref(); - value.as_ref().map(|key| { - key.to_public_key().unwrap_or_else(|err| { - eprintln!("Invalid {label} key: {}", err); - cli::safe_exit(1) - }) - }) -} - fn network_configs_url_prefix(chain_id: &ChainId) -> String { std::env::var(ENV_VAR_NETWORK_CONFIGS_SERVER).unwrap_or_else(|_| { format!("{DEFAULT_NETWORK_CONFIGS_SERVER}/{chain_id}") }) } +fn network_configs_dir() -> Option { + std::env::var(ENV_VAR_NETWORK_CONFIGS_DIR).ok() +} + /// Write the node key into tendermint config dir. pub fn write_tendermint_node_key( tm_home_dir: &Path, @@ -1173,9 +765,9 @@ pub fn write_tendermint_node_key( node_pk } -/// The default path to a validator pre-genesis file. -pub fn validator_pre_genesis_file(pre_genesis_path: &Path) -> PathBuf { - pre_genesis_path.join("validator.toml") +/// The default path to a validator pre-genesis txs file. +pub fn validator_pre_genesis_txs_file(pre_genesis_path: &Path) -> PathBuf { + pre_genesis_path.join("transactions.toml") } /// The default validator pre-genesis directory @@ -1183,6 +775,76 @@ pub fn validator_pre_genesis_dir(base_dir: &Path, alias: &str) -> PathBuf { base_dir.join(PRE_GENESIS_DIR).join(alias) } +/// Validate genesis templates. Exits process if invalid. +pub fn validate_genesis_templates( + _global_args: args::Global, + args::ValidateGenesisTemplates { path }: args::ValidateGenesisTemplates, +) { + if genesis::templates::load_and_validate(&path).is_none() { + safe_exit(1) + } +} + +/// Sign genesis transactions. +pub fn sign_genesis_tx( + global_args: args::Global, + args::SignGenesisTx { path, output }: args::SignGenesisTx, +) { + let (mut wallet, _wallet_file) = + load_pre_genesis_wallet_or_exit(&global_args.base_dir); + + let contents = fs::read(&path).unwrap_or_else(|err| { + eprintln!( + "Unable to read from file {}. Failed with {err}.", + path.to_string_lossy() + ); + safe_exit(1); + }); + let unsigned = genesis::transactions::parse_unsigned(&contents) + .unwrap_or_else(|err| { + eprintln!( + "Unable to parse the TOML from {}. Failed with {err}.", + path.to_string_lossy() + ); + safe_exit(1); + }); + if unsigned.validator_account.is_some() + && !unsigned.validator_account.as_ref().unwrap().is_empty() + { + eprintln!( + "Validator transactions must be signed with a validator wallet. \ + You can use `namada client utils init-genesis-validator` \ + supplied with the required arguments to generate a validator \ + wallet and sign the validator genesis transactions." + ); + safe_exit(1); + } + let signed = genesis::transactions::sign_txs(unsigned, &mut wallet); + + match output { + Some(output_path) => { + let transactions = toml::to_vec(&signed).unwrap(); + fs::write(&output_path, transactions).unwrap_or_else(|err| { + eprintln!( + "Failed to write output to {} with {err}.", + output_path.to_string_lossy() + ); + safe_exit(1); + }); + println!( + "Your public signed transactions TOML has been written to {}", + output_path.to_string_lossy() + ); + } + None => { + let transactions = toml::to_string(&signed).unwrap(); + println!("Your public signed transactions TOML:"); + println!(); + println!("{transactions}"); + } + } +} + /// Add a spinning wheel to a message for long running commands. /// Can be turned off for E2E tests by setting the `REDUCED_CLI_PRINTING` /// environment variable. @@ -1207,19 +869,6 @@ where task.join().unwrap() } -fn is_valid_validator_for_current_chain( - tendermint_node_pk: &common::PublicKey, - genesis_config: &GenesisConfig, -) -> bool { - genesis_config.validator.iter().any(|(_alias, config)| { - if let Some(tm_node_key) = &config.tendermint_node_key { - tm_node_key.0.eq(&tendermint_node_pk.to_string()) - } else { - false - } - }) -} - /// Replace the contents of `addr` with a dummy address. #[inline] pub fn take_config_address(addr: &mut TendermintAddress) -> TendermintAddress { @@ -1232,3 +881,13 @@ pub fn take_config_address(addr: &mut TendermintAddress) -> TendermintAddress { }, ) } + +#[cfg(not(test))] +fn safe_exit(code: i32) -> ! { + crate::cli::safe_exit(code) +} + +#[cfg(test)] +fn safe_exit(code: i32) -> ! { + panic!("Process exited unsuccesfully with error code: {}", code); +} diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index ed0ffeb788..a518686a0d 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -1,11 +1,20 @@ //! The parameters used for the chain's genesis +pub mod chain; +pub mod templates; +pub mod toml_utils; +pub mod transactions; + +use std::array::TryFromSliceError; use std::collections::{BTreeMap, HashMap}; use borsh::{BorshDeserialize, BorshSerialize}; +use data_encoding::HEXLOWER; use derivative::Derivative; use namada::core::ledger::governance::parameters::GovernanceParameters; use namada::core::ledger::pgf::parameters::PgfParameters; +use namada::core::types::string_encoding; +use namada::ledger::eth_bridge::EthereumBridgeParams; use namada::ledger::parameters::EpochDuration; use namada::ledger::pos::{Dec, GenesisValidator, OwnedPosParams}; use namada::types::address::Address; @@ -15,722 +24,10 @@ use namada::types::key::*; use namada::types::time::{DateTimeUtc, DurationSecs}; use namada::types::token::Denomination; use namada::types::{storage, token}; -use namada_sdk::eth_bridge::EthereumBridgeConfig; - -/// Genesis configuration file format -pub mod genesis_config { - use std::array::TryFromSliceError; - use std::collections::{BTreeMap, BTreeSet, HashMap}; - use std::convert::TryInto; - use std::path::Path; - use std::str::FromStr; - - use data_encoding::HEXLOWER; - use eyre::Context; - use namada::core::ledger::governance::parameters::GovernanceParameters; - use namada::core::ledger::pgf::parameters::PgfParameters; - use namada::ledger::parameters::EpochDuration; - use namada::ledger::pos::{Dec, GenesisValidator, OwnedPosParams}; - use namada::types::address::Address; - use namada::types::chain::ProposalBytes; - use namada::types::key::dkg_session_keys::DkgPublicKey; - use namada::types::key::*; - use namada::types::time::Rfc3339String; - use namada::types::token::Denomination; - use namada::types::{storage, token}; - use serde::{Deserialize, Serialize}; - use thiserror::Error; - - use super::{ - EstablishedAccount, EthereumBridgeConfig, Genesis, ImplicitAccount, - Parameters, TokenAccount, Validator, - }; - use crate::cli; - - #[derive(Clone, Debug, Deserialize, Serialize)] - pub struct HexString(pub String); - - impl HexString { - pub fn to_bytes(&self) -> Result, HexKeyError> { - let bytes = HEXLOWER.decode(self.0.as_ref())?; - Ok(bytes) - } - - pub fn to_sha256_bytes(&self) -> Result<[u8; 32], HexKeyError> { - let bytes = HEXLOWER.decode(self.0.as_ref())?; - let slice = bytes.as_slice(); - let array: [u8; 32] = slice.try_into()?; - Ok(array) - } - - pub fn to_public_key(&self) -> Result { - let key = common::PublicKey::from_str(&self.0) - .map_err(HexKeyError::InvalidPublicKey)?; - Ok(key) - } - - pub fn to_dkg_public_key(&self) -> Result { - let key = DkgPublicKey::from_str(&self.0)?; - Ok(key) - } - } - - #[derive(Error, Debug)] - pub enum HexKeyError { - #[error("Invalid hex string: {0:?}")] - InvalidHexString(data_encoding::DecodeError), - #[error("Invalid sha256 checksum: {0}")] - InvalidSha256(TryFromSliceError), - #[error("Invalid public key: {0}")] - InvalidPublicKey(ParsePublicKeyError), - } - - impl From for HexKeyError { - fn from(err: data_encoding::DecodeError) -> Self { - Self::InvalidHexString(err) - } - } - - impl From for HexKeyError { - fn from(err: ParsePublicKeyError) -> Self { - Self::InvalidPublicKey(err) - } - } - - impl From for HexKeyError { - fn from(err: TryFromSliceError) -> Self { - Self::InvalidSha256(err) - } - } - - #[derive(Clone, Debug, Deserialize, Serialize)] - pub struct GenesisConfig { - // Genesis timestamp - pub genesis_time: Rfc3339String, - // Name of the native token - this must one of the tokens included in - // the `token` field - pub native_token: String, - // Initial validator set - pub validator: HashMap, - // Token accounts present at genesis - pub token: HashMap, - // Established accounts present at genesis - pub established: Option>, - // Implicit accounts present at genesis - pub implicit: Option>, - // Protocol parameters - pub parameters: ParametersConfig, - // PoS parameters - pub pos_params: PosParamsConfig, - // Governance parameters - pub gov_params: GovernanceParamsConfig, - // Pgf parameters - pub pgf_params: PgfParametersConfig, - // Ethereum bridge config - pub ethereum_bridge_params: Option, - // Wasm definitions - pub wasm: HashMap, - } - - #[derive(Clone, Debug, Deserialize, Serialize)] - pub struct GovernanceParamsConfig { - // Min funds to stake to submit a proposal - pub min_proposal_fund: u64, - // Maximum size of proposal in kibibytes (KiB) - pub max_proposal_code_size: u64, - // Minimum proposal period length in epochs - pub min_proposal_voting_period: u64, - // Maximum proposal period length in epochs - pub max_proposal_period: u64, - // Maximum number of characters in the proposal content - pub max_proposal_content_size: u64, - // Minimum number of epoch between end and grace epoch - pub min_proposal_grace_epochs: u64, - } - - #[derive(Clone, Debug, Deserialize, Serialize)] - pub struct PgfParametersConfig { - /// The set of stewards - pub stewards: BTreeSet
, - /// The pgf inflation rate - pub pgf_inflation_rate: Dec, - /// The stewards inflation rate - pub stewards_inflation_rate: Dec, - } - - /// Validator pre-genesis configuration can be created with client utils - /// `init-genesis-validator` command and added to a genesis for - /// `init-network` cmd and that can be subsequently read by `join-network` - /// cmd to setup a genesis validator node. - #[derive(Serialize, Deserialize, Debug)] - pub struct ValidatorPreGenesisConfig { - pub validator: HashMap, - } - - #[derive(Clone, Default, Debug, Deserialize, Serialize)] - pub struct ValidatorConfig { - // Public key for consensus. (default: generate) - pub consensus_public_key: Option, - // Public key (cold) for eth governance. (default: generate) - pub eth_cold_key: Option, - // Public key (hot) for eth bridge. (default: generate) - pub eth_hot_key: Option, - // Public key for validator account. (default: generate) - pub account_public_key: Option, - // Public protocol signing key for validator account. (default: - // generate) - pub protocol_public_key: Option, - // Public DKG session key for validator account. (default: generate) - pub dkg_public_key: Option, - // Validator address (default: generate). - pub address: Option, - // Total number of tokens held at genesis. - pub tokens: Option, - // Unstaked balance at genesis. - pub non_staked_balance: Option, - /// Commission rate charged on rewards for delegators (bounded inside - /// 0-1) - pub commission_rate: Option, - /// Maximum change in commission rate permitted per epoch - pub max_commission_rate_change: Option, - // Filename of validator VP. (default: default validator VP) - pub validator_vp: Option, - // IP:port of the validator. (used in generation only) - pub net_address: Option, - /// Tendermint node key is used to derive Tendermint node ID for node - /// authentication - pub tendermint_node_key: Option, - } - - #[derive(Clone, Debug, Deserialize, Serialize)] - pub struct TokenAccountConfig { - // Address of token account (default: generate). - pub address: Option, - // The number of decimal places amounts of this token has - pub denom: Denomination, - // Filename of token account VP. (default: token VP) - pub vp: Option, - // Initial balances held by accounts defined elsewhere. - pub balances: Option>, - // Token parameters - pub parameters: Option, - } - - #[derive(Clone, Debug, Deserialize, Serialize)] - pub struct EstablishedAccountConfig { - // Address of established account (default: generate). - pub address: Option, - // Filename of established account VP. (default: user VP) - pub vp: Option, - // Public key of established account. (default: generate) - pub public_key: Option, - // Initial storage key values. - pub storage: Option>, - } - - #[derive(Clone, Debug, Deserialize, Serialize)] - pub struct ImplicitAccountConfig { - // Public key of implicit account (default: generate). - pub public_key: Option, - } - - #[derive(Clone, Debug, Deserialize, Serialize)] - pub struct ParametersConfig { - /// Max payload size, in bytes, for a tx batch proposal. - /// - /// Block proposers may never return a `PrepareProposal` - /// response containing `txs` with a byte length greater - /// than whatever is configured through this parameter. - /// - /// Note that this parameter's value will always be strictly - /// smaller than a Tendermint block's `MaxBytes` consensus - /// parameter. Currently, we hard cap `max_proposal_bytes` - /// at 90 MiB in Namada, which leaves at least 10 MiB of - /// room for header data, evidence and protobuf - /// serialization overhead in Tendermint blocks. - pub max_proposal_bytes: ProposalBytes, - /// Max block gas - pub max_block_gas: u64, - /// Minimum number of blocks per epoch. - pub min_num_of_blocks: u64, - /// Maximum duration per block (in seconds). - // TODO: this is i64 because datetime wants it - pub max_expected_time_per_block: i64, - /// Hashes of whitelisted vps array. `None` value or an empty array - /// disables whitelisting. - pub vp_whitelist: Option>, - /// Hashes of whitelisted txs array. `None` value or an empty array - /// disables whitelisting. - pub tx_whitelist: Option>, - /// Filename of implicit accounts validity predicate WASM code - pub implicit_vp: String, - /// Expected number of epochs per year - pub epochs_per_year: u64, - /// Max signature per transaction - pub max_signatures_per_transaction: u8, - /// PoS gain p - pub pos_gain_p: Dec, - /// PoS gain d - pub pos_gain_d: Dec, - /// Fee unshielding gas limit - pub fee_unshielding_gas_limit: u64, - /// Fee unshielding descriptions limit - pub fee_unshielding_descriptions_limit: u64, - /// Map of the cost per gas unit for every token allowed for fee - /// payment - pub minimum_gas_price: BTreeMap, - } - - #[derive(Clone, Debug, Deserialize, Serialize)] - pub struct PosParamsConfig { - // Maximum number of consensus validators. - pub max_validator_slots: u64, - // Pipeline length (in epochs). - pub pipeline_len: u64, - // Unbonding length (in epochs). - pub unbonding_len: u64, - // Votes per token. - pub tm_votes_per_token: Dec, - // Reward for proposing a block. - pub block_proposer_reward: Dec, - // Reward for voting on a block. - pub block_vote_reward: Dec, - // Maximum staking APY - pub max_inflation_rate: Dec, - // Target ratio of staked NAM tokens to total NAM tokens - pub target_staked_ratio: Dec, - // Portion of a validator's stake that should be slashed on a - // duplicate vote. - pub duplicate_vote_min_slash_rate: Dec, - // Portion of a validator's stake that should be slashed on a - // light client attack. - pub light_client_attack_min_slash_rate: Dec, - /// Number of epochs above and below (separately) the current epoch to - /// consider when doing cubic slashing - pub cubic_slashing_window_length: u64, - /// The minimum amount of bonded tokens that a validator needs to be in - /// either the `consensus` or `below_capacity` validator sets - pub validator_stake_threshold: token::Amount, - } - - #[derive(Clone, Debug, Deserialize, Serialize)] - pub struct WasmConfig { - filename: String, - pub sha256: Option, - } - - fn load_validator( - config: &ValidatorConfig, - wasm: &HashMap, - ) -> Validator { - let validator_vp_name = config.validator_vp.as_ref().unwrap(); - let validator_vp_config = wasm.get(validator_vp_name).unwrap(); +use serde::{Deserialize, Serialize}; - Validator { - pos_data: GenesisValidator { - address: Address::decode(config.address.as_ref().unwrap()) - .unwrap(), - tokens: token::Amount::native_whole( - config.tokens.unwrap_or_default(), - ), - consensus_key: config - .consensus_public_key - .as_ref() - .unwrap() - .to_public_key() - .unwrap(), - protocol_key: config - .protocol_public_key - .as_ref() - .unwrap() - .to_public_key() - .unwrap(), - eth_cold_key: config - .eth_cold_key - .as_ref() - .unwrap() - .to_public_key() - .unwrap(), - eth_hot_key: config - .eth_hot_key - .as_ref() - .unwrap() - .to_public_key() - .unwrap(), - commission_rate: config - .commission_rate - .and_then(|rate| { - if rate <= Dec::one() { Some(rate) } else { None } - }) - .expect("Commission rate must be between 0.0 and 1.0"), - max_commission_rate_change: config - .max_commission_rate_change - .and_then(|rate| { - if rate <= Dec::one() { Some(rate) } else { None } - }) - .expect( - "Max commission rate change must be between 0.0 and \ - 1.0", - ), - }, - account_key: config - .account_public_key - .as_ref() - .unwrap() - .to_public_key() - .unwrap(), - dkg_public_key: config - .dkg_public_key - .as_ref() - .unwrap() - .to_dkg_public_key() - .unwrap(), - non_staked_balance: token::Amount::native_whole( - config.non_staked_balance.unwrap_or_default(), - ), - validator_vp_code_path: validator_vp_config.filename.to_owned(), - validator_vp_sha256: validator_vp_config - .sha256 - .clone() - .unwrap() - .to_sha256_bytes() - .unwrap(), - } - } - - fn load_token( - config: &TokenAccountConfig, - validators: &HashMap, - established_accounts: &HashMap, - implicit_accounts: &HashMap, - ) -> TokenAccount { - TokenAccount { - last_locked_ratio: Dec::zero(), - last_inflation: token::Amount::zero(), - parameters: config.parameters.as_ref().unwrap().to_owned(), - address: Address::decode(config.address.as_ref().unwrap()).unwrap(), - denom: config.denom, - balances: config - .balances - .as_ref() - .unwrap_or(&HashMap::default()) - .iter() - .map(|(alias_or_address, amount)| { - ( - match Address::decode(alias_or_address) { - Ok(address) => address, - Err(decode_err) => { - if let Some(alias) = - alias_or_address.strip_suffix(".public_key") - { - if let Some(established) = - established_accounts.get(alias) - { - established - .public_key - .as_ref() - .unwrap() - .into() - } else if let Some(validator) = - validators.get(alias) - { - (&validator.account_key).into() - } else { - eprintln!( - "No established or validator \ - account with alias {} found", - alias - ); - cli::safe_exit(1) - } - } else if let Some(established) = - established_accounts.get(alias_or_address) - { - established.address.clone() - } else if let Some(validator) = - validators.get(alias_or_address) - { - validator.pos_data.address.clone() - } else if let Some(implicit) = - implicit_accounts.get(alias_or_address) - { - (&implicit.public_key).into() - } else { - eprintln!( - "{} is unknown alias and not a valid \ - address: {}", - alias_or_address, decode_err - ); - cli::safe_exit(1) - } - } - }, - token::Amount::from_uint(*amount, config.denom).expect( - "expected a balance that fits into 256 bits", - ), - ) - }) - .collect(), - } - } - - fn load_established( - config: &EstablishedAccountConfig, - wasm: &HashMap, - ) -> EstablishedAccount { - let account_vp_name = config.vp.as_ref().unwrap(); - let account_vp_config = wasm.get(account_vp_name).unwrap(); - - EstablishedAccount { - address: Address::decode(config.address.as_ref().unwrap()).unwrap(), - vp_code_path: account_vp_config.filename.to_owned(), - vp_sha256: account_vp_config - .sha256 - .clone() - .unwrap_or_else(|| { - eprintln!("Unknown user VP WASM sha256"); - cli::safe_exit(1); - }) - .to_sha256_bytes() - .unwrap(), - public_key: config - .public_key - .as_ref() - .map(|hex| hex.to_public_key().unwrap()), - storage: config - .storage - .as_ref() - .unwrap_or(&HashMap::default()) - .iter() - .map(|(address, hex)| { - ( - storage::Key::parse(address).unwrap(), - hex.to_bytes().unwrap(), - ) - }) - .collect(), - } - } - - fn load_implicit(config: &ImplicitAccountConfig) -> ImplicitAccount { - ImplicitAccount { - public_key: config - .public_key - .as_ref() - .unwrap() - .to_public_key() - .unwrap(), - } - } - - pub fn load_genesis_config(config: GenesisConfig) -> Genesis { - let GenesisConfig { - genesis_time, - native_token, - validator, - token, - established, - implicit, - parameters, - pos_params, - gov_params, - pgf_params, - wasm, - ethereum_bridge_params, - } = config; - - let native_token = Address::decode( - token - .get(&native_token) - .expect( - "Native token's alias must be present in the declared \ - tokens", - ) - .address - .as_ref() - .expect("Missing native token address"), - ) - .expect("Invalid address"); - let validators: HashMap = validator - .iter() - .map(|(name, cfg)| (name.clone(), load_validator(cfg, &wasm))) - .collect(); - let established_accounts: HashMap = - established - .unwrap_or_default() - .iter() - .map(|(name, cfg)| (name.clone(), load_established(cfg, &wasm))) - .collect(); - let implicit_accounts: HashMap = implicit - .unwrap_or_default() - .iter() - .map(|(name, cfg)| (name.clone(), load_implicit(cfg))) - .collect(); - #[allow(clippy::iter_kv_map)] - let token_accounts = token - .iter() - .map(|(_name, cfg)| { - load_token( - cfg, - &validators, - &established_accounts, - &implicit_accounts, - ) - }) - .collect(); - - let implicit_vp_config = wasm.get(¶meters.implicit_vp).unwrap(); - let implicit_vp_code_path = implicit_vp_config.filename.to_owned(); - let implicit_vp_sha256 = implicit_vp_config - .sha256 - .clone() - .unwrap_or_else(|| { - eprintln!("Unknown implicit VP WASM sha256"); - cli::safe_exit(1); - }) - .to_sha256_bytes() - .unwrap(); - - let min_duration: i64 = - 60 * 60 * 24 * 365 / (parameters.epochs_per_year as i64); - - let parameters = Parameters { - epoch_duration: EpochDuration { - min_num_of_blocks: parameters.min_num_of_blocks, - min_duration: namada::types::time::Duration::seconds( - min_duration, - ) - .into(), - }, - max_expected_time_per_block: - namada::types::time::Duration::seconds( - parameters.max_expected_time_per_block, - ) - .into(), - max_proposal_bytes: parameters.max_proposal_bytes, - max_block_gas: parameters.max_block_gas, - vp_whitelist: parameters.vp_whitelist.unwrap_or_default(), - tx_whitelist: parameters.tx_whitelist.unwrap_or_default(), - implicit_vp_code_path, - implicit_vp_sha256, - epochs_per_year: parameters.epochs_per_year, - max_signatures_per_transaction: parameters - .max_signatures_per_transaction, - pos_gain_p: parameters.pos_gain_p, - pos_gain_d: parameters.pos_gain_d, - staked_ratio: Dec::zero(), - pos_inflation_amount: token::Amount::zero(), - minimum_gas_price: parameters.minimum_gas_price, - fee_unshielding_gas_limit: parameters.fee_unshielding_gas_limit, - fee_unshielding_descriptions_limit: parameters - .fee_unshielding_descriptions_limit, - }; - - let GovernanceParamsConfig { - min_proposal_fund, - max_proposal_code_size, - min_proposal_voting_period, - max_proposal_content_size, - min_proposal_grace_epochs, - max_proposal_period, - } = gov_params; - let gov_params = GovernanceParameters { - min_proposal_fund: token::Amount::native_whole(min_proposal_fund), - max_proposal_code_size, - min_proposal_voting_period, - max_proposal_content_size, - min_proposal_grace_epochs, - max_proposal_period, - }; - - let PgfParametersConfig { - stewards, - pgf_inflation_rate, - stewards_inflation_rate, - } = pgf_params; - let pgf_params = PgfParameters { - stewards, - pgf_inflation_rate, - stewards_inflation_rate, - }; - - let PosParamsConfig { - max_validator_slots, - pipeline_len, - unbonding_len, - tm_votes_per_token, - block_proposer_reward, - block_vote_reward, - max_inflation_rate, - target_staked_ratio, - duplicate_vote_min_slash_rate, - light_client_attack_min_slash_rate, - cubic_slashing_window_length, - validator_stake_threshold, - } = pos_params; - - let pos_params = OwnedPosParams { - max_validator_slots, - pipeline_len, - unbonding_len, - tm_votes_per_token, - block_proposer_reward, - block_vote_reward, - max_inflation_rate, - target_staked_ratio, - duplicate_vote_min_slash_rate, - light_client_attack_min_slash_rate, - cubic_slashing_window_length, - validator_stake_threshold, - }; - - let mut genesis = Genesis { - genesis_time: genesis_time.try_into().unwrap(), - native_token, - validators: validators.into_values().collect(), - token_accounts, - established_accounts: established_accounts.into_values().collect(), - implicit_accounts: implicit_accounts.into_values().collect(), - parameters, - pos_params, - gov_params, - pgf_params, - ethereum_bridge_params, - }; - genesis.init(); - genesis - } - - pub fn open_genesis_config( - path: impl AsRef, - ) -> color_eyre::eyre::Result { - let config_file = - std::fs::read_to_string(&path).wrap_err_with(|| { - format!( - "couldn't read genesis config file from {}", - path.as_ref().to_string_lossy() - ) - })?; - toml::from_str(&config_file).wrap_err_with(|| { - format!( - "couldn't parse TOML from {}", - path.as_ref().to_string_lossy() - ) - }) - } - - pub fn write_genesis_config( - config: &GenesisConfig, - path: impl AsRef, - ) { - let toml = toml::to_string(&config).unwrap(); - std::fs::write(path, toml).unwrap(); - } - - pub fn read_genesis_config(path: impl AsRef) -> Genesis { - load_genesis_config(open_genesis_config(path).unwrap()) - } -} +#[cfg(all(any(test, feature = "benches"), not(feature = "integration")))] +use crate::config::genesis::chain::Finalized; #[derive(Debug, BorshSerialize, BorshDeserialize)] #[borsh(init=init)] @@ -746,7 +43,7 @@ pub struct Genesis { pub gov_params: GovernanceParameters, pub pgf_params: PgfParameters, // Ethereum bridge config - pub ethereum_bridge_params: Option, + pub ethereum_bridge_params: Option, } impl Genesis { @@ -895,254 +192,270 @@ pub struct Parameters { pub minimum_gas_price: BTreeMap, } -#[cfg(not(any(test, feature = "dev")))] -pub fn genesis( - base_dir: impl AsRef, - chain_id: &namada::types::chain::ChainId, -) -> Genesis { - let path = base_dir - .as_ref() - .join(format!("{}.toml", chain_id.as_str())); - genesis_config::read_genesis_config(path) +#[derive( + Clone, + Debug, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, + PartialOrd, + Ord, + PartialEq, + Eq, +)] +pub struct HexString(pub String); + +impl HexString { + pub fn parse(&self) -> Result, HexKeyError> { + let bytes = HEXLOWER.decode(self.0.as_ref())?; + Ok(bytes) + } } -#[cfg(any(test, feature = "dev"))] -pub fn genesis(num_validators: u64) -> Genesis { - use namada::types::address::{ - self, apfel, btc, dot, eth, kartoffel, nam, schnitzel, wnam, - }; - use namada::types::ethereum_events::testing::DAI_ERC20_ETH_ADDRESS; - use namada::types::ethereum_events::EthAddress; - use namada::types::uint::Uint; - use namada_sdk::eth_bridge::{ - Contracts, Erc20WhitelistEntry, UpgradeableContract, - }; - use crate::wallet; +#[derive(thiserror::Error, Debug)] +pub enum HexKeyError { + #[error("Invalid hex string: {0:?}")] + InvalidHexString(data_encoding::DecodeError), + #[error("Invalid sha256 checksum: {0}")] + InvalidSha256(TryFromSliceError), + #[error("Invalid public key: {0}")] + InvalidPublicKey(string_encoding::DecodeError), +} + +impl From for HexKeyError { + fn from(err: data_encoding::DecodeError) -> Self { + Self::InvalidHexString(err) + } +} + +impl From for HexKeyError { + fn from(err: string_encoding::DecodeError) -> Self { + Self::InvalidPublicKey(err) + } +} + +impl From for HexKeyError { + fn from(err: TryFromSliceError) -> Self { + Self::InvalidSha256(err) + } +} - let vp_implicit_path = "vp_implicit.wasm"; - let vp_user_path = "vp_user.wasm"; +/// Modify the default genesis file (namada/genesis/localnet/) to +/// accommodate testing. +/// +/// This includes adding the Ethereum bridge parameters and +/// adding a specified number of validators. +#[cfg(all(any(test, feature = "benches"), not(feature = "integration")))] +pub fn make_dev_genesis( + num_validators: u64, + target_chain_dir: std::path::PathBuf, +) -> Finalized { + use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + use std::str::FromStr; + use std::time::Duration; - // NOTE When the validator's key changes, tendermint must be reset with - // `namada reset` command. To generate a new validator, use the - // `tests::gen_genesis_validator` below. - let mut validators = Vec::::new(); + use namada::core::types::string_encoding::StringEncoded; + use namada::ledger::eth_bridge::{Contracts, UpgradeableContract}; + use namada::proto::{standalone_signature, SerializeWithBorsh}; + use namada::types::address::wnam; + use namada::types::chain::ChainIdPrefix; + use namada::types::ethereum_events::EthAddress; + use namada::types::token::NATIVE_MAX_DECIMAL_PLACES; + use namada_sdk::wallet::alias::Alias; + + use crate::config::genesis::chain::finalize; + use crate::config::genesis::transactions::UnsignedValidatorAccountTx; + use crate::wallet::defaults; + + let mut current_path = std::env::current_dir() + .expect("Current directory should exist") + .canonicalize() + .expect("Current directory should exist"); + // Find the project root dir + while !current_path.join("rust-toolchain.toml").exists() { + current_path.pop(); + } + let chain_dir = current_path.join("genesis").join("localnet"); + let templates = templates::load_and_validate(&chain_dir) + .expect("Missing genesis files"); + let mut genesis = finalize( + templates, + ChainIdPrefix::from_str("test").unwrap(), + DateTimeUtc::now(), + Duration::from_secs(30).into(), + ); + + // Add Ethereum bridge params. + genesis.parameters.eth_bridge_params = Some(templates::EthBridgeParams { + eth_start_height: Default::default(), + min_confirmations: Default::default(), + contracts: Contracts { + native_erc20: wnam(), + bridge: UpgradeableContract { + address: EthAddress([0; 20]), + version: Default::default(), + }, + }, + erc20_whitelist: vec![], + }); + + if let Some(vals) = genesis.transactions.validator_account.as_mut() { + let tx = vals.get_mut(0).unwrap(); + // Use the default validator address + tx.address = defaults::validator_address(); + + // Use the default validator key - have to add it with a sig + tx.address = defaults::validator_address(); + let sk = defaults::validator_keypair(); + let pk = StringEncoded::new(sk.to_public()); + let unsigned_tx = UnsignedValidatorAccountTx::from(&tx.tx); + let sig = transactions::sign_tx(&unsigned_tx, &sk); + tx.tx.account_key = transactions::SignedPk { + pk, + authorization: sig, + }; + } - // Use hard-coded keys for the first validator to avoid breaking other code - let consensus_keypair = wallet::defaults::validator_keypair(); - let account_keypair = wallet::defaults::validator_keypair(); + // Use the default address for matching established accounts + let default_addresses: HashMap = + defaults::addresses().into_iter().collect(); + if let Some(accs) = genesis.transactions.established_account.as_mut() { + for acc in accs { + if let Some(addr) = default_addresses.get(&acc.tx.alias) { + acc.address = addr.clone(); + } + } + } + + // Use the default token address for matching tokens + let default_tokens: HashMap = defaults::tokens() + .into_iter() + .map(|(address, alias)| (Alias::from(alias), address)) + .collect(); + for (alias, token) in genesis.tokens.token.iter_mut() { + if let Some(addr) = default_tokens.get(alias) { + token.address = addr.clone(); + } + } + + // remove Albert's bond since it messes up existing unit test math + if let Some(bonds) = genesis.transactions.bond.as_mut() { + bonds.retain(|bond| { + bond.source + != transactions::AliasOrPk::Alias( + Alias::from_str("albert").unwrap(), + ) + }) + }; let secp_eth_cold_keypair = secp256k1::SecretKey::try_from_slice(&[ 90, 83, 107, 155, 193, 251, 120, 27, 76, 1, 188, 8, 116, 121, 90, 99, 65, 17, 187, 6, 238, 141, 63, 188, 76, 38, 102, 7, 47, 185, 28, 52, ]) .unwrap(); - - let eth_cold_keypair = - common::SecretKey::try_from_sk(&secp_eth_cold_keypair).unwrap(); - let address = wallet::defaults::validator_address(); - let (protocol_keypair, eth_bridge_keypair, dkg_keypair) = - wallet::defaults::validator_keys(); - let validator = Validator { - pos_data: GenesisValidator { - address, - tokens: token::Amount::native_whole(200_000), - consensus_key: consensus_keypair.ref_to(), - protocol_key: protocol_keypair.ref_to(), - commission_rate: Dec::new(5, 2).expect("This can't fail"), - max_commission_rate_change: Dec::new(1, 2) - .expect("This can't fail"), - eth_cold_key: eth_cold_keypair.ref_to(), - eth_hot_key: eth_bridge_keypair.ref_to(), + let sign_pk = |sk: &common::SecretKey| transactions::SignedPk { + pk: StringEncoded { raw: sk.ref_to() }, + authorization: StringEncoded { + raw: standalone_signature::<_, SerializeWithBorsh>( + sk, + &sk.ref_to(), + ), }, - account_key: account_keypair.ref_to(), - dkg_public_key: dkg_keypair.public(), - non_staked_balance: token::Amount::native_whole(100_000), - // TODO replace with https://github.com/anoma/namada/issues/25) - validator_vp_code_path: vp_user_path.into(), - validator_vp_sha256: Default::default(), }; - validators.push(validator); - // Add other validators with randomly generated keys if needed - for _ in 0..(num_validators - 1) { + for val in 0..(num_validators - 1) { let consensus_keypair: common::SecretKey = testing::gen_keypair::() .try_to_sk() .unwrap(); let account_keypair = consensus_keypair.clone(); - let address = address::gen_established_address("validator account"); + let address = namada::types::address::gen_established_address( + "validator account", + ); let eth_cold_keypair = common::SecretKey::try_from_sk(&secp_eth_cold_keypair).unwrap(); let (protocol_keypair, eth_bridge_keypair, dkg_keypair) = - wallet::defaults::validator_keys(); - let validator = Validator { - pos_data: GenesisValidator { + defaults::validator_keys(); + let alias = Alias::from_str(&format!("validator-{}", val + 1)) + .expect("infallible"); + // add the validator + if let Some(vals) = genesis.transactions.validator_account.as_mut() { + vals.push(chain::FinalizedValidatorAccountTx { address, - tokens: token::Amount::native_whole(200_000), - consensus_key: consensus_keypair.ref_to(), - protocol_key: protocol_keypair.ref_to(), - commission_rate: Dec::new(5, 2).expect("This can't fail"), - max_commission_rate_change: Dec::new(1, 2) - .expect("This can't fail"), - eth_cold_key: eth_cold_keypair.ref_to(), - eth_hot_key: eth_bridge_keypair.ref_to(), - }, - account_key: account_keypair.ref_to(), - dkg_public_key: dkg_keypair.public(), - non_staked_balance: token::Amount::native_whole(100_000), - // TODO replace with https://github.com/anoma/namada/issues/25) - validator_vp_code_path: vp_user_path.into(), - validator_vp_sha256: Default::default(), + tx: transactions::ValidatorAccountTx { + alias: alias.clone(), + dkg_key: StringEncoded { + raw: dkg_keypair.public(), + }, + vp: "vp_validator".to_string(), + commission_rate: Dec::new(5, 2).expect("This can't fail"), + max_commission_rate_change: Dec::new(1, 2) + .expect("This can't fail"), + net_address: SocketAddr::new( + IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), + 8080, + ), + account_key: sign_pk(&account_keypair), + consensus_key: sign_pk(&consensus_keypair), + protocol_key: sign_pk(&protocol_keypair), + tendermint_node_key: sign_pk(&consensus_keypair), + eth_hot_key: sign_pk(ð_bridge_keypair), + eth_cold_key: sign_pk(ð_cold_keypair), + }, + }) }; - validators.push(validator); - } - - let parameters = Parameters { - epoch_duration: EpochDuration { - min_num_of_blocks: 10, - min_duration: namada::types::time::Duration::seconds(600).into(), - }, - max_expected_time_per_block: namada::types::time::DurationSecs(30), - max_proposal_bytes: Default::default(), - max_block_gas: 20_000_000, - vp_whitelist: vec![], - tx_whitelist: vec![], - implicit_vp_code_path: vp_implicit_path.into(), - implicit_vp_sha256: Default::default(), - max_signatures_per_transaction: 15, - epochs_per_year: 365, /* seconds in yr (60*60*24*365) div seconds - * per epoch (60 = min_duration) */ - pos_gain_p: Dec::new(1, 1).expect("This can't fail"), - pos_gain_d: Dec::new(1, 1).expect("This can't fail"), - staked_ratio: Dec::zero(), - pos_inflation_amount: token::Amount::zero(), - minimum_gas_price: [(nam(), token::Amount::from(1))] - .into_iter() - .collect(), - fee_unshielding_gas_limit: 20_000, - fee_unshielding_descriptions_limit: 15, - }; - let albert = EstablishedAccount { - address: wallet::defaults::albert_address(), - vp_code_path: vp_user_path.into(), - vp_sha256: Default::default(), - public_key: Some(wallet::defaults::albert_keypair().ref_to()), - storage: HashMap::default(), - }; - let bertha = EstablishedAccount { - address: wallet::defaults::bertha_address(), - vp_code_path: vp_user_path.into(), - vp_sha256: Default::default(), - public_key: Some(wallet::defaults::bertha_keypair().ref_to()), - storage: HashMap::default(), - }; - let christel = EstablishedAccount { - address: wallet::defaults::christel_address(), - vp_code_path: vp_user_path.into(), - vp_sha256: Default::default(), - public_key: Some(wallet::defaults::christel_keypair().ref_to()), - storage: HashMap::default(), - }; - let masp = EstablishedAccount { - address: namada::types::address::masp(), - vp_code_path: "vp_masp.wasm".into(), - vp_sha256: Default::default(), - public_key: None, - storage: HashMap::default(), - }; - let implicit_accounts = vec![ - ImplicitAccount { - public_key: wallet::defaults::daewon_keypair().ref_to(), - }, - ImplicitAccount { - public_key: wallet::defaults::ester_keypair().ref_to(), - }, - ]; - let default_user_tokens = Uint::from(1_000_000); - let default_key_tokens = Uint::from(1_000_000); - let mut balances: HashMap = HashMap::from_iter([ - // established accounts' balances - (wallet::defaults::albert_address(), default_user_tokens), - (wallet::defaults::bertha_address(), default_user_tokens), - (wallet::defaults::christel_address(), default_user_tokens), - // implicit accounts' balances - (wallet::defaults::daewon_address(), default_user_tokens), - // implicit accounts derived from public keys balances - ( - bertha.public_key.as_ref().unwrap().into(), - default_key_tokens, - ), - ( - albert.public_key.as_ref().unwrap().into(), - default_key_tokens, - ), - ( - christel.public_key.as_ref().unwrap().into(), - default_key_tokens, - ), - ]); - for validator in &validators { - balances.insert((&validator.account_key).into(), default_key_tokens); - } - - /// Deprecated function, soon to be deleted. Generates default tokens - fn tokens() -> HashMap { - vec![ - (nam(), ("NAM", 6.into())), - (btc(), ("BTC", 8.into())), - (eth(), ("ETH", 18.into())), - (dot(), ("DOT", 10.into())), - (schnitzel(), ("Schnitzel", 6.into())), - (apfel(), ("Apfel", 6.into())), - (kartoffel(), ("Kartoffel", 6.into())), - ] - .into_iter() - .collect() - } - let token_accounts = tokens() - .into_iter() - .map(|(address, (_, denom))| TokenAccount { - address, - denom, - balances: balances - .clone() - .into_iter() - .map(|(k, v)| (k, token::Amount::from_uint(v, denom).unwrap())) - .collect(), - parameters: token::Parameters::default(), - last_inflation: token::Amount::zero(), - last_locked_ratio: Dec::zero(), - }) - .collect(); - Genesis { - genesis_time: DateTimeUtc::now(), - validators, - established_accounts: vec![albert, bertha, christel, masp], - implicit_accounts, - token_accounts, - parameters, - pos_params: OwnedPosParams::default(), - gov_params: GovernanceParameters::default(), - pgf_params: PgfParameters::default(), - ethereum_bridge_params: Some(EthereumBridgeConfig { - erc20_whitelist: vec![Erc20WhitelistEntry { - token_address: DAI_ERC20_ETH_ADDRESS, - token_cap: token::DenominatedAmount { - amount: token::Amount::max(), - denom: 18.into(), + // add the balance to validators implicit key + if let Some(bals) = genesis + .balances + .token + .get_mut(&Alias::from_str("nam").unwrap()) + { + bals.0.insert( + StringEncoded { + raw: account_keypair.ref_to(), }, - }], - eth_start_height: Default::default(), - min_confirmations: Default::default(), - contracts: Contracts { - native_erc20: wnam(), - bridge: UpgradeableContract { - address: EthAddress([0; 20]), - version: Default::default(), + token::DenominatedAmount { + amount: token::Amount::native_whole(200_000), + denom: NATIVE_MAX_DECIMAL_PLACES.into(), }, - }, - }), - native_token: address::nam(), + ); + } + // transfer funds from implicit key to validator + if let Some(trans) = genesis.transactions.transfer.as_mut() { + trans.push(transactions::TransferTx { + token: Alias::from_str("nam").expect("infallible"), + source: StringEncoded { + raw: account_keypair.ref_to(), + }, + target: alias.clone(), + amount: token::DenominatedAmount { + amount: token::Amount::native_whole(200_000), + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }, + }) + } + // self bond + if let Some(bonds) = genesis.transactions.bond.as_mut() { + bonds.push(transactions::BondTx { + source: transactions::AliasOrPk::Alias(alias.clone()), + validator: alias, + amount: token::DenominatedAmount { + amount: token::Amount::native_whole(100_000), + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }, + }) + } } + + // Write out the TOML files for benches + #[cfg(feature = "benches")] + genesis + .write_toml_files(&target_chain_dir) + .expect("Must be able to write the finalized genesis"); + #[cfg(not(feature = "benches"))] + let _ = target_chain_dir; // avoid unused warn + + genesis } #[cfg(test)] diff --git a/apps/src/lib/config/genesis/chain.rs b/apps/src/lib/config/genesis/chain.rs new file mode 100644 index 0000000000..711266c542 --- /dev/null +++ b/apps/src/lib/config/genesis/chain.rs @@ -0,0 +1,874 @@ +use std::collections::BTreeMap; +use std::path::Path; +use std::str::FromStr; + +use borsh::{BorshDeserialize, BorshSerialize}; +use borsh_ext::BorshSerializeExt; +use namada::ledger::parameters::EpochDuration; +use namada::types::address::{masp, Address, EstablishedAddressGen}; +use namada::types::chain::{ChainId, ChainIdPrefix}; +use namada::types::dec::Dec; +use namada::types::hash::Hash; +use namada::types::time::{DateTimeUtc, DurationNanos, Rfc3339String}; +use namada::types::token::Amount; +use namada_sdk::wallet::store::AddressVpType; +use namada_sdk::wallet::{pre_genesis, Wallet}; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; + +use super::toml_utils::{read_toml, write_toml}; +use super::{templates, transactions}; +use crate::config::genesis::templates::Validated; +use crate::config::utils::{set_ip, set_port}; +use crate::config::{Config, TendermintMode}; +use crate::facade::tendermint::node::Id as TendermintNodeId; +use crate::facade::tendermint_config::net::Address as TendermintAddress; +use crate::node::ledger::tendermint_node::id_from_pk; +use crate::wallet::{Alias, CliWalletUtils}; +use crate::wasm_loader; + +pub const METADATA_FILE_NAME: &str = "chain.toml"; + +// Rng source used for generating genesis addresses. Because the process has to +// be deterministic, change of this value is a breaking change for genesis. +const ADDRESS_RNG_SOURCE: &[u8] = &[]; + +impl Finalized { + /// Write all genesis and the chain metadata TOML files to the given + /// directory. + pub fn write_toml_files(&self, output_dir: &Path) -> eyre::Result<()> { + let vps_file = output_dir.join(templates::VPS_FILE_NAME); + let tokens_file = output_dir.join(templates::TOKENS_FILE_NAME); + let balances_file = output_dir.join(templates::BALANCES_FILE_NAME); + let parameters_file = output_dir.join(templates::PARAMETERS_FILE_NAME); + let transactions_file = + output_dir.join(templates::TRANSACTIONS_FILE_NAME); + let metadata_file = output_dir.join(METADATA_FILE_NAME); + + write_toml(&self.vps, &vps_file, "Validity predicates")?; + write_toml(&self.tokens, &tokens_file, "Tokens")?; + write_toml(&self.balances, &balances_file, "Balances")?; + write_toml(&self.parameters, ¶meters_file, "Parameters")?; + write_toml(&self.transactions, &transactions_file, "Transactions")?; + write_toml(&self.metadata, &metadata_file, "Chain metadata")?; + Ok(()) + } + + /// Try to read all genesis and the chain metadata TOML files from the given + /// directory. + pub fn read_toml_files(input_dir: &Path) -> eyre::Result { + let vps_file = input_dir.join(templates::VPS_FILE_NAME); + let tokens_file = input_dir.join(templates::TOKENS_FILE_NAME); + let balances_file = input_dir.join(templates::BALANCES_FILE_NAME); + let parameters_file = input_dir.join(templates::PARAMETERS_FILE_NAME); + let transactions_file = + input_dir.join(templates::TRANSACTIONS_FILE_NAME); + let metadata_file = input_dir.join(METADATA_FILE_NAME); + + let vps = read_toml(&vps_file, "Validity predicates")?; + let tokens = read_toml(&tokens_file, "Tokens")?; + let balances = read_toml(&balances_file, "Balances")?; + let parameters = read_toml(¶meters_file, "Parameters")?; + let transactions = read_toml(&transactions_file, "Transactions")?; + let metadata = read_toml(&metadata_file, "Chain metadata")?; + Ok(Self { + vps, + tokens, + balances, + parameters, + transactions, + metadata, + }) + } + + /// Find the address of the configured native token + pub fn get_native_token(&self) -> &Address { + let alias = &self.parameters.parameters.native_token; + &self + .tokens + .token + .get(alias) + .expect("The native token must exist") + .address + } + + /// Derive Namada wallet from genesis + pub fn derive_wallet( + &self, + base_dir: &Path, + pre_genesis_wallet: Option>, + validator: Option<(Alias, pre_genesis::ValidatorWallet)>, + ) -> Wallet { + let mut wallet = crate::wallet::load_or_new(base_dir); + for (alias, config) in &self.tokens.token { + wallet.add_address( + alias.normalize(), + config.address.clone(), + false, + ); + wallet.add_vp_type_to_address( + AddressVpType::Token, + config.address.clone(), + ); + } + if let Some(txs) = &self.transactions.validator_account { + for tx in txs { + wallet.add_address( + tx.tx.alias.normalize(), + tx.address.clone(), + false, + ); + } + } + if let Some(txs) = &self.transactions.established_account { + for tx in txs { + wallet.add_address( + tx.tx.alias.normalize(), + tx.address.clone(), + false, + ); + } + } + if let Some(pre_genesis_wallet) = pre_genesis_wallet { + wallet.extend(pre_genesis_wallet); + } + if let Some((alias, validator_wallet)) = validator { + let address = self + .transactions + .find_validator(&alias) + .map(|tx| tx.address.clone()) + .expect("Validator alias not found in genesis transactions."); + wallet.extend_from_pre_genesis_validator( + address, + alias, + validator_wallet, + ) + } + wallet + } + + /// Derive Namada configuration from genesis + pub fn derive_config( + &self, + base_dir: &Path, + node_mode: TendermintMode, + validator_alias: Option, + allow_duplicate_ip: bool, + ) -> Config { + if node_mode != TendermintMode::Validator && validator_alias.is_some() { + println!( + "Warning: Validator alias used to derive config, but node \ + mode is not validator, it is {node_mode:?}!" + ); + } + let mut config = + Config::new(base_dir, self.metadata.chain_id.clone(), node_mode); + + // Derive persistent peers from genesis + let persistent_peers = self.derive_persistent_peers(); + // If `validator_wallet` is given, find its net_address + let validator_net_and_tm_address = + if let Some(alias) = validator_alias.as_ref() { + self.transactions.find_validator(alias).map(|validator_tx| { + ( + validator_tx.tx.net_address, + validator_tx.derive_tendermint_address(), + ) + }) + } else { + None + }; + // Check if the validators are localhost to automatically turn off + // Tendermint P2P address book strict mode to allow it + let is_localhost = persistent_peers.iter().all(|peer| match peer { + TendermintAddress::Tcp { + peer_id: _, + host, + port: _, + } => matches!(host.as_str(), "127.0.0.1" | "localhost"), + TendermintAddress::Unix { path: _ } => false, + }); + + // Configure the ledger + config.ledger.genesis_time = self.metadata.genesis_time.clone(); + + // Add a ledger P2P persistent peers + config.ledger.cometbft.p2p.persistent_peers = persistent_peers; + config.ledger.cometbft.consensus.timeout_commit = + self.metadata.consensus_timeout_commit.into(); + config.ledger.cometbft.p2p.allow_duplicate_ip = allow_duplicate_ip; + config.ledger.cometbft.p2p.addr_book_strict = !is_localhost; + + if let Some((net_address, tm_address)) = validator_net_and_tm_address { + // Take out address of self from the P2P persistent peers + config.ledger.cometbft.p2p.persistent_peers = config.ledger.cometbft.p2p.persistent_peers.iter() + .filter_map(|peer| + // we do not add the validator in its own persistent peer list + if peer != &tm_address { + Some(peer.to_owned()) + } else { + None + }) + .collect(); + + let first_port = net_address.port(); + if !is_localhost { + set_ip(&mut config.ledger.cometbft.p2p.laddr, "0.0.0.0"); + } + set_port(&mut config.ledger.cometbft.p2p.laddr, first_port); + if !is_localhost { + set_ip(&mut config.ledger.cometbft.rpc.laddr, "0.0.0.0"); + } + set_port(&mut config.ledger.cometbft.rpc.laddr, first_port + 1); + set_port(&mut config.ledger.cometbft.proxy_app, first_port + 2); + + // Validator node should turned off peer exchange reactor + config.ledger.cometbft.p2p.pex = false; + } + + config + } + + /// Derive persistent peers from genesis validators + fn derive_persistent_peers(&self) -> Vec { + self.transactions + .validator_account + .as_ref() + .map(|txs| { + txs.iter() + .map(FinalizedValidatorAccountTx::derive_tendermint_address) + .collect() + }) + .unwrap_or_default() + } + + /// Get the chain parameters set in genesis + pub fn get_chain_parameters( + &self, + wasm_dir: impl AsRef, + ) -> namada::ledger::parameters::Parameters { + let templates::ChainParams { + min_num_of_blocks, + max_expected_time_per_block, + max_proposal_bytes, + vp_whitelist, + tx_whitelist, + implicit_vp, + epochs_per_year, + pos_gain_p, + pos_gain_d, + max_signatures_per_transaction, + fee_unshielding_gas_limit, + fee_unshielding_descriptions_limit, + max_block_gas, + minimum_gas_price, + .. + } = self.parameters.parameters.clone(); + + let implicit_vp_filename = &self + .vps + .wasm + .get(&implicit_vp) + .expect("Implicit VP must be present") + .filename; + let implicit_vp = + wasm_loader::read_wasm(&wasm_dir, implicit_vp_filename) + .expect("Implicit VP WASM code couldn't get read"); + let implicit_vp_code_hash = Hash::sha256(implicit_vp); + + let min_duration: i64 = 60 * 60 * 24 * 365 / (epochs_per_year as i64); + let epoch_duration = EpochDuration { + min_num_of_blocks, + min_duration: namada::types::time::Duration::seconds(min_duration) + .into(), + }; + let max_expected_time_per_block = + namada::types::time::Duration::seconds(max_expected_time_per_block) + .into(); + let vp_whitelist = vp_whitelist.unwrap_or_default(); + let tx_whitelist = tx_whitelist.unwrap_or_default(); + let staked_ratio = Dec::zero(); + let pos_inflation_amount = 0; + + namada::ledger::parameters::Parameters { + epoch_duration, + max_expected_time_per_block, + vp_whitelist, + tx_whitelist, + implicit_vp_code_hash, + epochs_per_year, + pos_gain_p, + pos_gain_d, + staked_ratio, + pos_inflation_amount: Amount::native_whole(pos_inflation_amount), + max_proposal_bytes, + max_signatures_per_transaction, + fee_unshielding_gas_limit, + fee_unshielding_descriptions_limit, + max_block_gas, + minimum_gas_price: minimum_gas_price + .iter() + .map(|(token, amt)| { + ( + self.tokens.token.get(token).cloned().unwrap().address, + amt.amount, + ) + }) + .collect(), + } + } + + pub fn get_pos_params( + &self, + ) -> namada::proof_of_stake::parameters::PosParams { + let templates::PosParams { + max_validator_slots, + pipeline_len, + unbonding_len, + tm_votes_per_token, + block_proposer_reward, + block_vote_reward, + max_inflation_rate, + target_staked_ratio, + duplicate_vote_min_slash_rate, + light_client_attack_min_slash_rate, + cubic_slashing_window_length, + validator_stake_threshold, + } = self.parameters.pos_params.clone(); + + namada::proof_of_stake::parameters::PosParams { + owned: namada::proof_of_stake::parameters::OwnedPosParams { + max_validator_slots, + pipeline_len, + unbonding_len, + tm_votes_per_token, + block_proposer_reward, + block_vote_reward, + max_inflation_rate, + target_staked_ratio, + duplicate_vote_min_slash_rate, + light_client_attack_min_slash_rate, + cubic_slashing_window_length, + validator_stake_threshold, + }, + max_proposal_period: self.parameters.gov_params.max_proposal_period, + } + } + + pub fn get_gov_params( + &self, + ) -> namada::core::ledger::governance::parameters::GovernanceParameters + { + let templates::GovernanceParams { + min_proposal_fund, + max_proposal_code_size, + min_proposal_voting_period, + max_proposal_period, + max_proposal_content_size, + min_proposal_grace_epochs, + } = self.parameters.gov_params.clone(); + namada::core::ledger::governance::parameters::GovernanceParameters { + min_proposal_fund: Amount::native_whole(min_proposal_fund), + max_proposal_code_size, + max_proposal_period, + max_proposal_content_size, + min_proposal_grace_epochs, + min_proposal_voting_period, + } + } + + pub fn get_pgf_params( + &self, + ) -> namada::core::ledger::pgf::parameters::PgfParameters { + self.parameters.pgf_params.clone() + } + + pub fn get_eth_bridge_params( + &self, + ) -> Option { + if let Some(templates::EthBridgeParams { + eth_start_height, + min_confirmations, + contracts, + erc20_whitelist, + }) = self.parameters.eth_bridge_params.clone() + { + Some(namada::ledger::eth_bridge::EthereumBridgeParams { + eth_start_height, + min_confirmations, + erc20_whitelist, + contracts, + }) + } else { + None + } + } + + pub fn get_token_address(&self, alias: &Alias) -> Option<&Address> { + self.tokens.token.get(alias).map(|token| &token.address) + } + + pub fn get_user_address(&self, alias: &Alias) -> Option
{ + if alias.to_string() == *"masp" { + return Some(masp()); + } + let established = self.transactions.established_account.as_ref()?; + let validators = self.transactions.validator_account.as_ref()?; + established + .iter() + .find_map(|tx| { + (&tx.tx.alias == alias).then_some(tx.address.clone()) + }) + .or_else(|| { + validators.iter().find_map(|tx| { + (&tx.tx.alias == alias).then_some(tx.address.clone()) + }) + }) + } + + pub fn get_validator_address(&self, alias: &Alias) -> Option<&Address> { + let validators = self.transactions.validator_account.as_ref()?; + validators + .iter() + .find_map(|tx| (&tx.tx.alias == alias).then_some(&tx.address)) + } +} + +/// Create the [`Finalized`] chain configuration. Derives the chain ID from the +/// genesis bytes and assigns addresses to tokens and transactions that +/// initialize established accounts. +/// +/// Invariant: The output must deterministic. For the same input this function +/// must return the same output. +pub fn finalize( + templates: templates::All, + chain_id_prefix: ChainIdPrefix, + genesis_time: DateTimeUtc, + consensus_timeout_commit: crate::facade::tendermint::Timeout, +) -> Finalized { + let genesis_time: Rfc3339String = genesis_time.into(); + let consensus_timeout_commit: DurationNanos = + consensus_timeout_commit.into(); + + // Derive seed for address generator + let genesis_to_gen_address = GenesisToGenAddresses { + templates, + metadata: Metadata { + chain_id: chain_id_prefix.clone(), + genesis_time, + consensus_timeout_commit, + address_gen: None, + }, + }; + let genesis_bytes = genesis_to_gen_address.serialize_to_vec(); + let mut addr_gen = established_address_gen(&genesis_bytes); + + // Generate addresses + let templates::All { + vps, + tokens, + balances, + parameters, + transactions, + } = genesis_to_gen_address.templates; + let tokens = FinalizedTokens::finalize_from(tokens, &mut addr_gen); + let transactions = + FinalizedTransactions::finalize_from(transactions, &mut addr_gen); + let parameters = + FinalizedParameters::finalize_from(&transactions, parameters); + + // Store the last state of the address generator in the metadata + let mut metadata = genesis_to_gen_address.metadata; + metadata.address_gen = Some(addr_gen); + + // Derive chain ID + let to_finalize = ToFinalize { + metadata, + vps, + tokens, + balances, + parameters, + transactions, + }; + let to_finalize_bytes = to_finalize.serialize_to_vec(); + let chain_id = ChainId::from_genesis(chain_id_prefix, to_finalize_bytes); + + // Construct the `Finalized` chain + let ToFinalize { + vps, + tokens, + balances, + parameters, + transactions, + metadata, + } = to_finalize; + let Metadata { + chain_id: _, + genesis_time, + consensus_timeout_commit, + address_gen, + } = metadata; + let metadata = Metadata { + chain_id, + genesis_time, + consensus_timeout_commit, + address_gen, + }; + Finalized { + metadata, + vps, + tokens, + balances, + parameters, + transactions, + } +} + +/// Use bytes as a deterministic seed for address generator. +fn established_address_gen(bytes: &[u8]) -> EstablishedAddressGen { + let mut hasher = Sha256::new(); + hasher.update(bytes); + // hex of the first 40 chars of the hash + let hash = format!("{:.width$X}", hasher.finalize(), width = 40); + EstablishedAddressGen::new(hash) +} + +/// Deterministically generate an [`Address`]. +fn gen_address(gen: &mut EstablishedAddressGen) -> Address { + gen.generate_address(ADDRESS_RNG_SOURCE) +} + +/// Chain genesis config to be finalized. This struct is used to derive the +/// chain ID to construct a [`Finalized`] chain genesis config. +#[derive( + Clone, Debug, Deserialize, Serialize, BorshDeserialize, BorshSerialize, +)] +pub struct GenesisToGenAddresses { + /// Filled-in templates + pub templates: templates::All, + /// Chain metadata + pub metadata: Metadata, +} + +/// Chain genesis config to be finalized. This struct is used to derive the +/// chain ID to construct a [`Finalized`] chain genesis config. +pub type ToFinalize = Chain; + +/// Chain genesis config. +pub type Finalized = Chain; + +/// Chain genesis config with generic chain ID. +#[derive( + Clone, + Debug, + Deserialize, + Serialize, + BorshDeserialize, + BorshSerialize, + PartialEq, + Eq, +)] +pub struct Chain { + pub vps: templates::ValidityPredicates, + pub tokens: FinalizedTokens, + pub balances: templates::DenominatedBalances, + pub parameters: FinalizedParameters, + pub transactions: FinalizedTransactions, + /// Chain metadata + pub metadata: Metadata, +} + +#[derive( + Clone, + Debug, + Deserialize, + Serialize, + BorshDeserialize, + BorshSerialize, + PartialEq, + Eq, +)] +pub struct FinalizedTokens { + pub token: BTreeMap, +} + +impl FinalizedTokens { + fn finalize_from( + tokens: templates::Tokens, + addr_gen: &mut EstablishedAddressGen, + ) -> FinalizedTokens { + let templates::Tokens { token } = tokens; + let token = token + .into_iter() + .map(|(key, config)| { + let address = gen_address(addr_gen); + (key, FinalizedTokenConfig { address, config }) + }) + .collect(); + FinalizedTokens { token } + } +} + +#[derive( + Clone, + Debug, + Deserialize, + Serialize, + BorshDeserialize, + BorshSerialize, + PartialEq, + Eq, +)] +pub struct FinalizedTokenConfig { + pub address: Address, + #[serde(flatten)] + pub config: templates::TokenConfig, +} + +#[derive( + Clone, + Debug, + Default, + Deserialize, + Serialize, + BorshDeserialize, + BorshSerialize, + PartialEq, + Eq, +)] +pub struct FinalizedTransactions { + pub established_account: Option>, + pub validator_account: Option>, + pub transfer: Option>>, + pub bond: Option>>, +} + +impl FinalizedTransactions { + fn finalize_from( + transactions: transactions::Transactions, + addr_gen: &mut EstablishedAddressGen, + ) -> FinalizedTransactions { + let transactions::Transactions { + established_account, + validator_account, + transfer, + bond, + } = transactions; + let established_account = established_account.map(|txs| { + txs.into_iter() + .map(|tx| { + let address = gen_address(addr_gen); + FinalizedEstablishedAccountTx { address, tx } + }) + .collect() + }); + let validator_account = validator_account.map(|txs| { + txs.into_iter() + .map(|tx| { + let address = gen_address(addr_gen); + FinalizedValidatorAccountTx { address, tx } + }) + .collect() + }); + FinalizedTransactions { + established_account, + validator_account, + transfer, + bond, + } + } + + fn find_validator( + &self, + alias: &Alias, + ) -> Option<&FinalizedValidatorAccountTx> { + self.validator_account + .as_ref() + .and_then(|txs| txs.iter().find(|tx| &tx.tx.alias == alias)) + } +} + +#[derive( + Clone, + Debug, + Deserialize, + Serialize, + BorshDeserialize, + BorshSerialize, + PartialEq, + Eq, +)] +pub struct FinalizedParameters { + pub parameters: templates::ChainParams, + pub pos_params: templates::PosParams, + pub gov_params: templates::GovernanceParams, + pub pgf_params: namada::core::ledger::pgf::parameters::PgfParameters, + pub eth_bridge_params: Option, +} + +impl FinalizedParameters { + fn finalize_from( + txs: &FinalizedTransactions, + templates::Parameters { + parameters, + pos_params, + gov_params, + pgf_params, + eth_bridge_params, + }: templates::Parameters, + ) -> Self { + use namada::core::ledger::pgf::parameters::PgfParameters; + let mut finalized_pgf_params = PgfParameters { + stewards: Default::default(), + pgf_inflation_rate: pgf_params.pgf_inflation_rate, + stewards_inflation_rate: pgf_params.stewards_inflation_rate, + }; + finalized_pgf_params.stewards = pgf_params + .stewards + .into_iter() + .map(|alias| { + let maybe_estbd = txs + .established_account + .as_ref() + .unwrap() + .iter() + .find(|tx| tx.tx.alias == alias) + .map(|tx| tx.address.clone()); + let maybe_validator = txs + .validator_account + .as_ref() + .unwrap() + .iter() + .find(|tx| tx.tx.alias == alias) + .map(|tx| tx.address.clone()); + maybe_estbd.or(maybe_validator).unwrap() + }) + .collect(); + Self { + parameters, + pos_params, + gov_params, + pgf_params: finalized_pgf_params, + eth_bridge_params, + } + } +} + +#[derive( + Clone, + Debug, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, + PartialEq, + Eq, +)] +pub struct FinalizedEstablishedAccountTx { + pub address: Address, + #[serde(flatten)] + pub tx: transactions::SignedEstablishedAccountTx, +} + +#[derive( + Clone, + Debug, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, + PartialEq, + Eq, +)] +pub struct FinalizedValidatorAccountTx { + pub address: Address, + #[serde(flatten)] + pub tx: transactions::SignedValidatorAccountTx, +} + +impl FinalizedValidatorAccountTx { + pub fn derive_tendermint_address(&self) -> TendermintAddress { + // Derive the node ID from the node key + let node_id: TendermintNodeId = + id_from_pk(&self.tx.tendermint_node_key.pk.raw); + + // Build the list of persistent peers from the validators' node IDs + TendermintAddress::from_str(&format!( + "{}@{}", + node_id, self.tx.net_address, + )) + .expect("Validator address must be valid") + } +} + +/// Chain metadata +#[derive( + Clone, + Debug, + Deserialize, + Serialize, + BorshDeserialize, + BorshSerialize, + PartialEq, + Eq, +)] +pub struct Metadata { + /// Chain ID in [`Finalized`] or chain ID prefix in + /// [`GenesisToGenAddresses`] and [`ToFinalize`]. + pub chain_id: ID, + // Genesis timestamp + pub genesis_time: Rfc3339String, + /// The Tendermint consensus timeout_commit configuration + pub consensus_timeout_commit: DurationNanos, + /// This generator should be used to initialize the ledger for the + /// next address that will be generated on chain. + /// + /// The value is expected to always be `None` in [`GenesisToGenAddresses`] + /// and `Some` in [`ToFinalize`] and [`Finalized`]. + pub address_gen: Option, +} + +#[cfg(test)] +mod test { + use std::path::PathBuf; + use std::str::FromStr; + + use super::*; + + /// Test that the [`finalize`] returns deterministic output with the same + /// chain ID for the same input. + #[test] + fn test_finalize_is_deterministic() { + // Load the localnet templates + let templates_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .parent() + .unwrap() + .join("genesis/localnet"); + let templates = templates::load_and_validate(&templates_dir).unwrap(); + + let chain_id_prefix: ChainIdPrefix = + FromStr::from_str("test-prefix").unwrap(); + + let genesis_time = + DateTimeUtc::from_str("2021-12-31T00:00:00Z").unwrap(); + + let consensus_timeout_commit = + crate::facade::tendermint::Timeout::from_str("1s").unwrap(); + + let finalized_0 = finalize( + templates.clone(), + chain_id_prefix.clone(), + genesis_time, + consensus_timeout_commit, + ); + + let finalized_1 = finalize( + templates, + chain_id_prefix, + genesis_time, + consensus_timeout_commit, + ); + + pretty_assertions::assert_eq!(finalized_0, finalized_1); + } +} diff --git a/apps/src/lib/config/genesis/templates.rs b/apps/src/lib/config/genesis/templates.rs new file mode 100644 index 0000000000..d4012163e8 --- /dev/null +++ b/apps/src/lib/config/genesis/templates.rs @@ -0,0 +1,992 @@ +//! The templates for balances, parameters and VPs used for a chain's genesis. + +use std::collections::{BTreeMap, BTreeSet}; +use std::marker::PhantomData; +use std::path::Path; + +use borsh::{BorshDeserialize, BorshSerialize}; +use namada::core::types::key::common; +use namada::core::types::string_encoding::StringEncoded; +use namada::core::types::{ethereum_structs, token}; +use namada::eth_bridge::parameters::{ + Contracts, Erc20WhitelistEntry, MinimumConfirmations, +}; +use namada::types::chain::ProposalBytes; +use namada::types::dec::Dec; +use namada::types::token::{ + Amount, DenominatedAmount, Denomination, NATIVE_MAX_DECIMAL_PLACES, +}; +use serde::{Deserialize, Serialize}; + +use super::toml_utils::{read_toml, write_toml}; +use super::transactions::{self, Transactions}; +use crate::config::genesis::transactions::{ + BondTx, SignedBondTx, SignedTransferTx, TransferTx, +}; +use crate::wallet::Alias; + +pub const BALANCES_FILE_NAME: &str = "balances.toml"; +pub const PARAMETERS_FILE_NAME: &str = "parameters.toml"; +pub const VPS_FILE_NAME: &str = "validity-predicates.toml"; +pub const TOKENS_FILE_NAME: &str = "tokens.toml"; +pub const TRANSACTIONS_FILE_NAME: &str = "transactions.toml"; + +const MAX_TOKEN_BALANCE_SUM: u64 = i64::MAX as u64; + +/// Note that these balances must be crossed-checked with the token configs +/// to correctly represent the underlying amounts. +pub fn read_balances(path: &Path) -> eyre::Result { + read_toml(path, "Balances") +} + +pub fn read_parameters(path: &Path) -> eyre::Result> { + read_toml(path, "Parameters") +} + +pub fn read_validity_predicates( + path: &Path, +) -> eyre::Result { + read_toml(path, "Validity predicates") +} + +pub fn read_tokens(path: &Path) -> eyre::Result { + read_toml(path, "Tokens") +} + +pub fn read_transactions( + path: &Path, +) -> eyre::Result> { + read_toml(path, "Transactions") +} + +/// Genesis balances of all tokens +#[derive( + Clone, + Debug, + Deserialize, + Serialize, + BorshDeserialize, + BorshSerialize, + PartialEq, + Eq, +)] +pub struct UndenominatedBalances { + pub token: BTreeMap, +} + +impl UndenominatedBalances { + /// Use the denom in `TokenConfig` to correctly interpret the balances + /// to the right denomination. + pub fn denominate( + self, + tokens: &Tokens, + ) -> eyre::Result { + let mut balances = DenominatedBalances { + token: BTreeMap::new(), + }; + for (alias, bals) in self.token { + let denom = tokens + .token + .get(&alias) + .ok_or_else(|| { + eyre::eyre!( + "A balance of token {} was found, but this token was \ + not found in the `tokens.toml` file", + alias + ) + })? + .denom; + let mut denominated_bals = BTreeMap::new(); + for (pk, bal) in bals.0.into_iter() { + let denominated = bal.increase_precision(denom)?; + denominated_bals.insert(pk, denominated); + } + balances + .token + .insert(alias, TokenBalances(denominated_bals)); + } + Ok(balances) + } +} + +/// Genesis balances of all tokens +#[derive( + Clone, + Debug, + Deserialize, + Serialize, + BorshDeserialize, + BorshSerialize, + PartialEq, + Eq, +)] +pub struct DenominatedBalances { + pub token: BTreeMap, +} + +/// Genesis balances for a given token +#[derive( + Clone, + Debug, + Deserialize, + Serialize, + BorshDeserialize, + BorshSerialize, + PartialEq, + Eq, +)] +pub struct RawTokenBalances( + pub BTreeMap, token::DenominatedAmount>, +); + +/// Genesis balances for a given token +#[derive( + Clone, + Debug, + Deserialize, + Serialize, + BorshDeserialize, + BorshSerialize, + PartialEq, + Eq, +)] +pub struct TokenBalances( + pub BTreeMap, token::DenominatedAmount>, +); + +/// Genesis validity predicates +#[derive( + Clone, + Debug, + Deserialize, + Serialize, + BorshDeserialize, + BorshSerialize, + PartialEq, + Eq, +)] +pub struct ValidityPredicates { + // Wasm definitions + pub wasm: BTreeMap, +} + +#[derive( + Clone, + Debug, + Deserialize, + Serialize, + BorshDeserialize, + BorshSerialize, + PartialEq, + Eq, +)] +pub struct WasmVpConfig { + pub filename: String, +} + +#[derive( + Clone, + Debug, + Deserialize, + Serialize, + BorshDeserialize, + BorshSerialize, + PartialEq, + Eq, +)] +pub struct Tokens { + pub token: BTreeMap, +} + +#[derive( + Clone, + Debug, + Deserialize, + Serialize, + BorshDeserialize, + BorshSerialize, + PartialEq, + Eq, +)] +pub struct TokenConfig { + pub denom: Denomination, + pub parameters: token::Parameters, +} + +#[derive( + Clone, + Debug, + Deserialize, + Serialize, + BorshDeserialize, + BorshSerialize, + PartialEq, + Eq, +)] +pub struct Parameters { + pub parameters: ChainParams, + pub pos_params: PosParams, + pub gov_params: GovernanceParams, + pub pgf_params: PgfParams, + pub eth_bridge_params: Option, +} + +#[derive( + Clone, + Debug, + Deserialize, + Serialize, + BorshDeserialize, + BorshSerialize, + PartialEq, + Eq, +)] +pub struct ChainParams { + /// Name of the native token - this must one of the tokens from + /// `tokens.toml` file + pub native_token: Alias, + /// Minimum number of blocks per epoch. + // TODO: u64 only works with values up to i64::MAX with toml-rs! + pub min_num_of_blocks: u64, + /// Maximum duration per block (in seconds). + // TODO: this is i64 because datetime wants it + pub max_expected_time_per_block: i64, + /// Max payload size, in bytes, for a tx batch proposal. + /// + /// Block proposers may never return a `PrepareProposal` + /// response containing `txs` with a byte length greater + /// than whatever is configured through this parameter. + /// + /// Note that this parameter's value will always be strictly + /// smaller than a Tendermint block's `MaxBytes` consensus + /// parameter. Currently, we hard cap `max_proposal_bytes` + /// at 90 MiB in Namada, which leaves at least 10 MiB of + /// room for header data, evidence and protobuf + /// serialization overhead in Tendermint blocks. + pub max_proposal_bytes: ProposalBytes, + /// Hashes of whitelisted vps array. `None` value or an empty array + /// disables whitelisting. + pub vp_whitelist: Option>, + /// Hashes of whitelisted txs array. `None` value or an empty array + /// disables whitelisting. + pub tx_whitelist: Option>, + /// Filename of implicit accounts validity predicate WASM code + pub implicit_vp: String, + /// Expected number of epochs per year + pub epochs_per_year: u64, + /// PoS gain p + pub pos_gain_p: Dec, + /// PoS gain d + pub pos_gain_d: Dec, + /// Maximum number of signature per transaction + pub max_signatures_per_transaction: u8, + /// Max gas for block + pub max_block_gas: u64, + /// Fee unshielding gas limit + pub fee_unshielding_gas_limit: u64, + /// Fee unshielding descriptions limit + pub fee_unshielding_descriptions_limit: u64, + /// Map of the cost per gas unit for every token allowed for fee payment + pub minimum_gas_price: T::GasMinimums, +} + +impl ChainParams { + pub fn denominate( + self, + tokens: &Tokens, + ) -> eyre::Result> { + let ChainParams { + native_token, + min_num_of_blocks, + max_expected_time_per_block, + max_proposal_bytes, + vp_whitelist, + tx_whitelist, + implicit_vp, + epochs_per_year, + pos_gain_p, + pos_gain_d, + max_signatures_per_transaction, + max_block_gas, + fee_unshielding_gas_limit, + fee_unshielding_descriptions_limit, + minimum_gas_price, + } = self; + let mut min_gas_prices = BTreeMap::default(); + for (token, amount) in minimum_gas_price.into_iter() { + let denom = if let Some(TokenConfig { denom, .. }) = + tokens.token.get(&token) + { + *denom + } else { + eprintln!( + "Genesis files contained minimum gas amount of token {}, \ + which is not in the `tokens.toml` file", + token + ); + return Err(eyre::eyre!( + "Genesis files contained minimum gas amount of token {}, \ + which is not in the `tokens.toml` file", + token + )); + }; + let amount = amount.increase_precision(denom).map_err(|e| { + eprintln!( + "A minimum gas amount in the parameters.toml file was \ + incorrectly formatted:\n{}", + e + ); + e + })?; + min_gas_prices.insert(token, amount); + } + + Ok(ChainParams { + native_token, + min_num_of_blocks, + max_expected_time_per_block, + max_proposal_bytes, + vp_whitelist, + tx_whitelist, + implicit_vp, + epochs_per_year, + pos_gain_p, + pos_gain_d, + max_signatures_per_transaction, + max_block_gas, + fee_unshielding_gas_limit, + fee_unshielding_descriptions_limit, + minimum_gas_price: min_gas_prices, + }) + } +} + +#[derive( + Clone, + Debug, + Deserialize, + Serialize, + BorshDeserialize, + BorshSerialize, + PartialEq, + Eq, +)] +pub struct PosParams { + /// Maximum number of active validators. + pub max_validator_slots: u64, + /// Pipeline length (in epochs). + pub pipeline_len: u64, + /// Unbonding length (in epochs). + pub unbonding_len: u64, + /// Votes per token. + pub tm_votes_per_token: Dec, + /// Reward for proposing a block. + pub block_proposer_reward: Dec, + /// Reward for voting on a block. + pub block_vote_reward: Dec, + /// Maximum staking APY + pub max_inflation_rate: Dec, + /// Target ratio of staked NAM tokens to total NAM tokens + pub target_staked_ratio: Dec, + /// Portion of a validator's stake that should be slashed on a + /// duplicate vote. + pub duplicate_vote_min_slash_rate: Dec, + /// Portion of a validator's stake that should be slashed on a + /// light client attack. + pub light_client_attack_min_slash_rate: Dec, + /// Number of epochs above and below (separately) the current epoch to + /// consider when doing cubic slashing + pub cubic_slashing_window_length: u64, + /// The minimum amount of bonded tokens that a validator needs to be in + /// either the `consensus` or `below_capacity` validator sets + pub validator_stake_threshold: token::Amount, +} + +#[derive( + Clone, + Debug, + Deserialize, + Serialize, + BorshDeserialize, + BorshSerialize, + PartialEq, + Eq, +)] +pub struct GovernanceParams { + /// Min funds to stake to submit a proposal + pub min_proposal_fund: u64, + /// Maximum size of proposal in kibibytes (KiB) + pub max_proposal_code_size: u64, + /// Minimum proposal period length in epochs + pub min_proposal_voting_period: u64, + /// Maximum proposal period length in epochs + pub max_proposal_period: u64, + /// Maximum number of characters in the proposal content + pub max_proposal_content_size: u64, + /// Minimum number of epoch between end and grace epoch + pub min_proposal_grace_epochs: u64, +} + +#[derive( + Clone, + Debug, + Deserialize, + Serialize, + BorshDeserialize, + BorshSerialize, + PartialEq, + Eq, +)] +pub struct PgfParams { + /// The set of stewards + pub stewards: BTreeSet, + /// The pgf funding inflation rate + pub pgf_inflation_rate: Dec, + /// The pgf stewards inflation rate + pub stewards_inflation_rate: Dec, + #[serde(default)] + #[serde(skip_serializing)] + #[cfg(test)] + pub valid: PhantomData, + #[serde(default)] + #[serde(skip_serializing)] + #[cfg(not(test))] + valid: PhantomData, +} + +#[derive( + Clone, + Debug, + Deserialize, + Serialize, + BorshDeserialize, + BorshSerialize, + PartialEq, + Eq, +)] +pub struct EthBridgeParams { + /// Initial Ethereum block height when events will first be extracted from. + pub eth_start_height: ethereum_structs::BlockHeight, + /// Minimum number of confirmations needed to trust an Ethereum branch. + /// This must be at least one. + pub min_confirmations: MinimumConfirmations, + /// List of ERC20 token types whitelisted at genesis time. + pub erc20_whitelist: Vec, + /// The addresses of the Ethereum contracts that need to be directly known + /// by validators. + pub contracts: Contracts, +} + +impl TokenBalances { + pub fn get(&self, pk: common::PublicKey) -> Option { + let pk = StringEncoded { raw: pk }; + self.0.get(&pk).map(|amt| amt.amount) + } +} +#[derive( + Clone, + Debug, + Deserialize, + Serialize, + BorshDeserialize, + BorshSerialize, + PartialEq, + Eq, +)] +pub struct Unvalidated {} + +#[derive( + Clone, + Debug, + Deserialize, + Serialize, + BorshDeserialize, + BorshSerialize, + PartialEq, + Eq, +)] +pub struct Validated {} + +pub trait TemplateValidation: Serialize { + type Amount: for<'a> Deserialize<'a> + + Serialize + + Clone + + std::fmt::Debug + + BorshSerialize + + BorshDeserialize + + PartialEq + + Eq; + type Balances: for<'a> Deserialize<'a> + + Serialize + + Clone + + std::fmt::Debug + + BorshSerialize + + BorshDeserialize + + PartialEq + + Eq; + type TransferTx: for<'a> Deserialize<'a> + + Serialize + + Clone + + std::fmt::Debug + + BorshSerialize + + BorshDeserialize + + PartialEq + + Eq; + type BondTx: for<'a> Deserialize<'a> + + Serialize + + Clone + + std::fmt::Debug + + BorshSerialize + + BorshDeserialize + + PartialEq + + Eq; + type GasMinimums: for<'a> Deserialize<'a> + + Serialize + + Clone + + std::fmt::Debug + + BorshSerialize + + BorshDeserialize + + PartialEq + + Eq; +} + +impl TemplateValidation for Unvalidated { + type Amount = token::DenominatedAmount; + type Balances = UndenominatedBalances; + type BondTx = SignedBondTx; + type GasMinimums = BTreeMap; + type TransferTx = SignedTransferTx; +} + +impl TemplateValidation for Validated { + type Amount = token::DenominatedAmount; + type Balances = DenominatedBalances; + type BondTx = BondTx; + type GasMinimums = BTreeMap; + type TransferTx = TransferTx; +} + +#[derive( + Clone, + Debug, + Deserialize, + Serialize, + BorshDeserialize, + BorshSerialize, + PartialEq, + Eq, +)] +pub struct All { + pub vps: ValidityPredicates, + pub tokens: Tokens, + pub balances: T::Balances, + pub parameters: Parameters, + pub transactions: Transactions, +} + +impl All { + pub fn write_toml_files(&self, output_dir: &Path) -> eyre::Result<()> { + let All { + vps, + tokens, + balances, + parameters, + transactions, + } = self; + + let vps_file = output_dir.join(VPS_FILE_NAME); + let tokens_file = output_dir.join(TOKENS_FILE_NAME); + let balances_file = output_dir.join(BALANCES_FILE_NAME); + let parameters_file = output_dir.join(PARAMETERS_FILE_NAME); + let transactions_file = output_dir.join(TRANSACTIONS_FILE_NAME); + + write_toml(vps, &vps_file, "Validity predicates")?; + write_toml(tokens, &tokens_file, "Tokens")?; + write_toml(balances, &balances_file, "Balances")?; + write_toml(parameters, ¶meters_file, "Parameters")?; + write_toml(transactions, &transactions_file, "Transactions")?; + Ok(()) + } +} + +impl All { + pub fn read_toml_files(input_dir: &Path) -> eyre::Result { + let vps_file = input_dir.join(VPS_FILE_NAME); + let tokens_file = input_dir.join(TOKENS_FILE_NAME); + let balances_file = input_dir.join(BALANCES_FILE_NAME); + let parameters_file = input_dir.join(PARAMETERS_FILE_NAME); + let transactions_file = input_dir.join(TRANSACTIONS_FILE_NAME); + + let vps = read_toml(&vps_file, "Validity predicates")?; + let tokens = read_toml(&tokens_file, "Tokens")?; + let balances = read_toml(&balances_file, "Balances")?; + let parameters = read_toml(¶meters_file, "Parameters")?; + let transactions = read_toml(&transactions_file, "Transactions")?; + Ok(Self { + vps, + tokens, + balances, + parameters, + transactions, + }) + } +} + +/// Load genesis templates from the given directory and validate them. Returns +/// `None` when there are some validation issues. +/// +/// Note that the validation rules for these templates won't enforce that there +/// is at least one validator with positive voting power. This must be checked +/// when the templates are being used to `init-network`. +pub fn load_and_validate(templates_dir: &Path) -> Option> { + let mut is_valid = true; + // We don't reuse `All::read_toml_files` here to allow to validate config + // without all files present. + let vps_file = templates_dir.join(VPS_FILE_NAME); + let tokens_file = templates_dir.join(TOKENS_FILE_NAME); + let balances_file = templates_dir.join(BALANCES_FILE_NAME); + let parameters_file = templates_dir.join(PARAMETERS_FILE_NAME); + let transactions_file = templates_dir.join(TRANSACTIONS_FILE_NAME); + + // Check that all required files are present + let mut check_file_exists = |file: &Path, name: &str| { + if !file.exists() { + is_valid = false; + eprintln!("{name} file is missing at {}", file.to_string_lossy()); + } + }; + check_file_exists(&vps_file, "Validity predicates"); + check_file_exists(&tokens_file, "Tokens"); + check_file_exists(&balances_file, "Balances"); + check_file_exists(¶meters_file, "Parameters"); + check_file_exists(&transactions_file, "Transactions"); + + // Load and parse the files + let vps = read_validity_predicates(&vps_file); + let tokens = read_tokens(&tokens_file); + let balances = read_balances(&balances_file); + let parameters = read_parameters(¶meters_file); + let transactions = read_transactions(&transactions_file); + + let eprintln_invalid_file = |err: &eyre::Report, name: &str| { + eprintln!("{name} file is NOT valid. Failed to read with: {err}"); + }; + + // Check the parsing results + let vps = vps.map_or_else( + |err| { + eprintln_invalid_file(&err, "Validity predicates"); + None + }, + Some, + ); + let tokens = tokens.map_or_else( + |err| { + eprintln_invalid_file(&err, "Tokens"); + None + }, + Some, + ); + let balances = balances.map_or_else( + |err| { + eprintln_invalid_file(&err, "Balances"); + None + }, + Some, + ); + let parameters = parameters.map_or_else( + |err| { + eprintln_invalid_file(&err, "Parameters"); + None + }, + Some, + ); + let transactions = transactions.map_or_else( + |err| { + eprintln_invalid_file(&err, "Transactions"); + None + }, + Some, + ); + + // Validate each file that could be loaded + if let Some(vps) = vps.as_ref() { + if validate_vps(vps) { + println!("Validity predicates file is valid."); + } else { + is_valid = false; + } + } + + let parameters = parameters.and_then(|params| { + let params = + validate_parameters(params, &tokens, &transactions, vps.as_ref()); + if params.is_some() { + println!("Parameters file is valid."); + } else { + is_valid = false + } + params + }); + + let balances = if let Some(tokens) = tokens.as_ref() { + if tokens.token.is_empty() { + is_valid = false; + eprintln!( + "Tokens file is invalid. There has to be at least one token." + ); + } + println!("Tokens file is valid."); + balances + .and_then(|raw| raw.denominate(tokens).ok()) + .and_then(|balances| { + validate_balances(&balances, Some(tokens)).then(|| { + println!("Balances file is valid."); + balances + }) + }) + } else { + None + }; + if balances.is_none() { + is_valid = false; + } + + let txs = if let Some(tokens) = tokens.as_ref() { + if let Some(txs) = transactions.and_then(|txs| { + transactions::validate( + txs, + vps.as_ref(), + balances.as_ref(), + tokens, + parameters.as_ref(), + ) + }) { + println!("Transactions file is valid."); + Some(txs) + } else { + is_valid = false; + None + } + } else { + is_valid = false; + None + }; + + match vps { + Some(vps) if is_valid => Some(All { + vps, + tokens: tokens.unwrap(), + balances: balances.unwrap(), + parameters: parameters.unwrap(), + transactions: txs.unwrap(), + }), + _ => None, + } +} + +pub fn validate_vps(vps: &ValidityPredicates) -> bool { + let mut is_valid = true; + vps.wasm.iter().for_each(|(name, config)| { + if !config.filename.ends_with(".wasm") { + eprintln!( + "Invalid validity predicate \"{name}\" configuration. Only \ + \".wasm\" filenames are currently supported." + ); + is_valid = false; + } + }); + is_valid +} + +pub fn validate_parameters( + parameters: Parameters, + tokens: &Option, + transactions: &Option>, + vps: Option<&ValidityPredicates>, +) -> Option> { + let tokens = tokens.as_ref()?; + let txs = transactions.as_ref()?; + let mut is_valid = true; + let implicit_vp = ¶meters.parameters.implicit_vp; + if !vps + .map(|vps| vps.wasm.contains_key(implicit_vp)) + .unwrap_or_default() + { + eprintln!( + "Implicit VP \"{implicit_vp}\" not found in the Validity \ + predicates files." + ); + is_valid = false; + } + // check that each PGF steward has an established account + for steward in ¶meters.pgf_params.stewards { + let mut found_steward = false; + if let Some(accs) = &txs.established_account { + if accs.iter().any(|acct| acct.alias == *steward) { + found_steward = true; + } + } + + if let Some(accs) = &txs.validator_account { + if accs.iter().any(|acct| acct.alias == *steward) { + found_steward = true; + } + } + is_valid = found_steward && is_valid; + if !is_valid { + eprintln!( + "Could not find an established or validator account \ + associated with the PGF steward {}", + steward + ); + } + } + let Parameters { + parameters, + pos_params, + gov_params, + pgf_params, + eth_bridge_params, + } = parameters; + match parameters.denominate(tokens) { + Err(e) => { + eprintln!("{}", e); + None + } + Ok(parameters) => is_valid.then(|| Parameters { + parameters, + pos_params, + gov_params, + pgf_params: PgfParams { + stewards: pgf_params.stewards, + pgf_inflation_rate: pgf_params.pgf_inflation_rate, + stewards_inflation_rate: pgf_params.stewards_inflation_rate, + valid: Default::default(), + }, + eth_bridge_params, + }), + } +} + +pub fn validate_balances( + balances: &DenominatedBalances, + tokens: Option<&Tokens>, +) -> bool { + let mut is_valid = true; + use std::str::FromStr; + let native_alias = Alias::from_str("nam").expect("Infalllible"); + balances.token.iter().for_each(|(token, next)| { + // Every token alias used in Balances file must be present in + // the Tokens file + if !tokens + .as_ref() + .map(|tokens| tokens.token.contains_key(token)) + .unwrap_or_default() + { + is_valid = false; + eprintln!( + "Token \"{token}\" from the Balances file is not present in \ + the Tokens file." + ) + } + + // Check the sum of balances + let sum = next.0.values().try_fold( + token::Amount::default(), + |acc, amount| { + let res = acc.checked_add(amount.amount); + if res.as_ref().is_none() { + is_valid = false; + eprintln!( + "Balances for token {token} overflow `token::Amount`" + ); + } + res + }, + ); + if sum.is_none() + || (*token == native_alias + && sum.unwrap() + > Amount::from_uint( + MAX_TOKEN_BALANCE_SUM, + NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap()) + { + eprintln!( + "The sum of balances for token {token} is greater than \ + {MAX_TOKEN_BALANCE_SUM}" + ); + is_valid = false; + } + }); + is_valid +} + +#[cfg(test)] +mod tests { + use std::fs; + use std::path::PathBuf; + + use namada::core::types::key; + use namada::types::key::RefTo; + use tempfile::tempdir; + + use super::*; + + /// Validate the `genesis/localnet` genesis templates. + #[test] + fn test_validate_localnet_genesis_templates() { + let templates_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .parent() + .unwrap() + .join("genesis/localnet"); + assert!( + load_and_validate(&templates_dir).is_some(), + "Localnet genesis templates must be valid" + ); + } + + /// Validate the `genesis/starter` genesis templates. + #[test] + fn test_validate_starter_genesis_templates() { + let templates_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .parent() + .unwrap() + .join("genesis/starter"); + assert!( + load_and_validate(&templates_dir).is_some(), + "Starter genesis templates must be valid" + ); + } + + #[test] + fn test_read_balances() { + let test_dir = tempdir().unwrap(); + let path = test_dir.path().join(BALANCES_FILE_NAME); + let sk = key::testing::keypair_1(); + let pk = sk.ref_to(); + let balance = token::Amount::from(101_000_001); + let token_alias = Alias::from("Some_token".to_string()); + let contents = format!( + r#" + [token.{token_alias}] + {pk} = "{}" + "#, + balance.to_string_native() + ); + fs::write(&path, contents).unwrap(); + + let balances = read_balances(&path).unwrap(); + let example_balance = balances.token.get(&token_alias).unwrap(); + assert_eq!( + balance, + example_balance + .0 + .get(&StringEncoded { raw: pk }) + .unwrap() + .amount + ); + } +} diff --git a/apps/src/lib/config/genesis/toml_utils.rs b/apps/src/lib/config/genesis/toml_utils.rs new file mode 100644 index 0000000000..e699341951 --- /dev/null +++ b/apps/src/lib/config/genesis/toml_utils.rs @@ -0,0 +1,38 @@ +use std::path::Path; + +use eyre::Context; +use serde::de::DeserializeOwned; +use serde::Serialize; + +pub fn read_toml( + path: &Path, + which_file: &str, +) -> eyre::Result { + let file_contents = std::fs::read_to_string(path).wrap_err_with(|| { + format!( + "Couldn't read {which_file} config file from {}", + path.to_string_lossy() + ) + })?; + toml::from_str(&file_contents).wrap_err_with(|| { + format!( + "Couldn't parse {which_file} TOML from {}", + path.to_string_lossy() + ) + }) +} + +pub fn write_toml( + data: &T, + path: &Path, + which_file: &str, +) -> eyre::Result<()> { + let file_contents = toml::to_vec(data) + .wrap_err_with(|| format!("Couldn't format {which_file} to TOML."))?; + std::fs::write(path, file_contents).wrap_err_with(|| { + format!( + "Couldn't write {which_file} TOML to {}", + path.to_string_lossy() + ) + }) +} diff --git a/apps/src/lib/config/genesis/transactions.rs b/apps/src/lib/config/genesis/transactions.rs new file mode 100644 index 0000000000..21db15f43a --- /dev/null +++ b/apps/src/lib/config/genesis/transactions.rs @@ -0,0 +1,1484 @@ +//! Genesis transactions + +use std::collections::{BTreeMap, BTreeSet, HashMap}; +use std::fmt::{Debug, Display, Formatter}; +use std::net::SocketAddr; +use std::str::FromStr; + +use borsh::{BorshDeserialize, BorshSerialize}; +use borsh_ext::BorshSerializeExt; +use namada::core::types::storage; +use namada::core::types::string_encoding::StringEncoded; +use namada::proto::{ + standalone_signature, verify_standalone_sig, SerializeWithBorsh, +}; +use namada::types::dec::Dec; +use namada::types::key::dkg_session_keys::DkgPublicKey; +use namada::types::key::{common, RefTo, VerifySigError}; +use namada::types::time::{DateTimeUtc, MIN_UTC}; +use namada::types::token; +use namada::types::token::{DenominatedAmount, NATIVE_MAX_DECIMAL_PLACES}; +use namada_sdk::wallet::pre_genesis::ValidatorWallet; +use namada_sdk::wallet::{FindKeyError, Wallet}; +use serde::{Deserialize, Serialize}; + +use super::templates::{ + DenominatedBalances, Parameters, TokenBalances, ValidityPredicates, +}; +use crate::config::genesis::templates::{ + TemplateValidation, Tokens, Unvalidated, Validated, +}; +use crate::config::genesis::HexString; +use crate::wallet::{Alias, CliWalletUtils}; + +pub const PRE_GENESIS_TX_TIMESTAMP: DateTimeUtc = MIN_UTC; + +pub struct GenesisValidatorData { + pub source_key: common::SecretKey, + pub alias: Alias, + pub commission_rate: Dec, + pub max_commission_rate_change: Dec, + pub net_address: SocketAddr, + pub transfer_from_source_amount: token::DenominatedAmount, + pub self_bond_amount: token::DenominatedAmount, +} + +/// Panics if given `txs.validator_accounts` is not empty, because validator +/// transactions must be signed with a validator wallet (see +/// `init-genesis-validator` command). +pub fn sign_txs( + txs: UnsignedTransactions, + wallet: &mut Wallet, +) -> Transactions { + let UnsignedTransactions { + established_account, + validator_account, + transfer, + bond, + } = txs; + + // Validate input first + if validator_account.is_some() && !validator_account.unwrap().is_empty() { + panic!( + "Validator transactions must be signed with a validator wallet." + ); + } + + if let Some(bonds) = bond.as_ref() { + for bond in bonds { + if let AliasOrPk::Alias(source) = &bond.source { + if source == &bond.validator { + panic!( + "Validator self-bonds must be signed with a validator \ + wallet." + ) + } + } + } + } + + // Sign all the transactions + let established_account = established_account.map(|tx| { + tx.into_iter() + .map(|tx| sign_established_account_tx(tx, wallet)) + .collect() + }); + let validator_account = None; + let transfer = transfer.map(|tx| { + tx.into_iter() + .map(|tx| sign_transfer_tx(tx, wallet)) + .collect() + }); + let bond = bond.map(|tx| { + tx.into_iter() + .map(|tx| sign_delegation_bond_tx(tx, wallet, &established_account)) + .collect() + }); + + Transactions { + established_account, + validator_account, + transfer, + bond, + } +} + +/// Parse [`UnsignedTransactions`] from bytes. +pub fn parse_unsigned( + bytes: &[u8], +) -> Result { + toml::from_slice(bytes) +} + +/// Create signed [`Transactions`] for a genesis validator. +pub fn init_validator( + GenesisValidatorData { + source_key, + alias, + commission_rate, + max_commission_rate_change, + net_address, + transfer_from_source_amount, + self_bond_amount, + }: GenesisValidatorData, + source_wallet: &mut Wallet, + validator_wallet: &ValidatorWallet, +) -> Transactions { + let unsigned_validator_account_tx = UnsignedValidatorAccountTx { + alias: alias.clone(), + account_key: StringEncoded::new(validator_wallet.account_key.ref_to()), + consensus_key: StringEncoded::new( + validator_wallet.consensus_key.ref_to(), + ), + protocol_key: StringEncoded::new( + validator_wallet + .store + .validator_keys + .protocol_keypair + .ref_to(), + ), + dkg_key: StringEncoded::new( + validator_wallet + .store + .validator_keys + .dkg_keypair + .as_ref() + .expect("Missing validator DKG key") + .public(), + ), + tendermint_node_key: StringEncoded::new( + validator_wallet.tendermint_node_key.ref_to(), + ), + + eth_hot_key: StringEncoded::new(validator_wallet.eth_hot_key.ref_to()), + eth_cold_key: StringEncoded::new( + validator_wallet.eth_cold_key.ref_to(), + ), + // No custom validator VPs yet + vp: "vp_validator".to_string(), + commission_rate, + max_commission_rate_change, + net_address, + }; + let validator_account = Some(vec![sign_validator_account_tx( + unsigned_validator_account_tx, + validator_wallet, + )]); + + let transfer = if transfer_from_source_amount.amount.is_zero() { + None + } else { + let unsigned_transfer_tx = TransferTx { + // Only native token can be staked + token: Alias::from("NAM"), + source: StringEncoded::new(source_key.ref_to()), + target: alias.clone(), + amount: transfer_from_source_amount, + }; + let transfer_tx = sign_transfer_tx(unsigned_transfer_tx, source_wallet); + Some(vec![transfer_tx]) + }; + + let bond = if self_bond_amount.amount.is_zero() { + None + } else { + let unsigned_bond_tx = BondTx { + source: AliasOrPk::Alias(alias.clone()), + validator: alias, + amount: self_bond_amount, + }; + let bond_tx = sign_self_bond_tx(unsigned_bond_tx, validator_wallet); + Some(vec![bond_tx]) + }; + + Transactions { + validator_account, + transfer, + bond, + ..Default::default() + } +} + +pub fn sign_established_account_tx( + unsigned_tx: UnsignedEstablishedAccountTx, + wallet: &mut Wallet, +) -> SignedEstablishedAccountTx { + let key = unsigned_tx.public_key.as_ref().map(|pk| { + let secret = wallet + .find_key_by_pk(pk, None) + .expect("Key for source must be present to sign with it."); + let sig = sign_tx(&unsigned_tx, &secret); + SignedPk { + pk: pk.clone(), + authorization: sig, + } + }); + let UnsignedEstablishedAccountTx { + alias, + vp, + public_key: _, + storage, + } = unsigned_tx; + + SignedEstablishedAccountTx { + alias, + vp, + public_key: key, + storage, + } +} + +pub fn sign_validator_account_tx( + unsigned_tx: UnsignedValidatorAccountTx, + validator_wallet: &ValidatorWallet, +) -> SignedValidatorAccountTx { + // Sign the tx with every validator key to authorize their usage + let account_key_sig = sign_tx(&unsigned_tx, &validator_wallet.account_key); + let consensus_key_sig = + sign_tx(&unsigned_tx, &validator_wallet.consensus_key); + let protocol_key_sig = sign_tx( + &unsigned_tx, + &validator_wallet.store.validator_keys.protocol_keypair, + ); + let eth_hot_key_sig = sign_tx(&unsigned_tx, &validator_wallet.eth_hot_key); + let eth_cold_key_sig = + sign_tx(&unsigned_tx, &validator_wallet.eth_cold_key); + let tendermint_node_key_sig = + sign_tx(&unsigned_tx, &validator_wallet.tendermint_node_key); + + let ValidatorAccountTx { + alias, + account_key, + consensus_key, + protocol_key, + dkg_key, + tendermint_node_key, + vp, + commission_rate, + max_commission_rate_change, + net_address, + eth_hot_key, + eth_cold_key, + } = unsigned_tx; + + let account_key = SignedPk { + pk: account_key, + authorization: account_key_sig, + }; + let consensus_key = SignedPk { + pk: consensus_key, + authorization: consensus_key_sig, + }; + let protocol_key = SignedPk { + pk: protocol_key, + authorization: protocol_key_sig, + }; + let tendermint_node_key = SignedPk { + pk: tendermint_node_key, + authorization: tendermint_node_key_sig, + }; + + let eth_hot_key = SignedPk { + pk: eth_hot_key, + authorization: eth_hot_key_sig, + }; + + let eth_cold_key = SignedPk { + pk: eth_cold_key, + authorization: eth_cold_key_sig, + }; + + SignedValidatorAccountTx { + alias, + account_key, + consensus_key, + protocol_key, + dkg_key, + tendermint_node_key, + vp, + commission_rate, + max_commission_rate_change, + net_address, + eth_hot_key, + eth_cold_key, + } +} + +pub fn sign_transfer_tx( + unsigned_tx: TransferTx, + source_wallet: &mut Wallet, +) -> SignedTransferTx { + let source_key = source_wallet + .find_key_by_pk(&unsigned_tx.source, None) + .expect("Key for source must be present to sign with it."); + unsigned_tx.sign(&source_key) +} + +pub fn sign_self_bond_tx( + unsigned_tx: BondTx, + validator_wallet: &ValidatorWallet, +) -> SignedBondTx { + unsigned_tx.sign(&validator_wallet.account_key) +} + +pub fn sign_delegation_bond_tx( + unsigned_tx: BondTx, + wallet: &mut Wallet, + established_accounts: &Option>>, +) -> SignedBondTx { + let alias = &unsigned_tx.source; + // Try to look-up the source from wallet first - if it's an alias of an + // implicit account that should give us the right key + let found_key = match alias { + AliasOrPk::Alias(alias) => wallet.find_key(&alias.normalize(), None), + AliasOrPk::PublicKey(pk) => wallet.find_key_by_pk(pk, None), + }; + let source_key = match found_key { + Ok(key) => key, + Err(FindKeyError::KeyNotFound) => { + // If it's not in the wallet, it must be an established account + // so we need to look-up its public key first + let pk = established_accounts + .as_ref() + .unwrap_or_else(|| { + panic!( + "Signing a bond failed. Cannot find \"{alias}\" in \ + the wallet and there are no established accounts." + ); + }) + .iter() + .find_map(|account| match alias { + AliasOrPk::Alias(alias) => { + // delegation from established account + if &account.alias == alias { + Some( + &account + .public_key + .as_ref() + .unwrap_or_else(|| { + panic!( + "Signing a bond failed. The \ + established account \"{alias}\" \ + has no public key. Add a public \ + to be able to sign bonds." + ); + }) + .pk + .raw, + ) + } else { + None + } + } + AliasOrPk::PublicKey(pk) => { + // delegation from an implicit account + Some(&pk.raw) + } + }) + .unwrap_or_else(|| { + panic!( + "Signing a bond failed. Cannot find \"{alias}\" in \ + the wallet or in the established accounts." + ); + }); + wallet.find_key_by_pk(pk, None).unwrap_or_else(|err| { + panic!( + "Signing a bond failed. Cannot find key for established \ + account \"{alias}\" in the wallet. Failed with {err}." + ); + }) + } + Err(err) => panic!( + "Signing a bond failed. Failed to read the key for \"{alias}\" \ + from wallet with {err}." + ), + }; + unsigned_tx.sign(&source_key) +} + +pub fn sign_tx( + tx_data: &T, + keypair: &common::SecretKey, +) -> StringEncoded { + StringEncoded::new(namada::proto::standalone_signature::< + T, + SerializeWithBorsh, + >(keypair, tx_data)) +} + +#[derive( + Clone, + Debug, + Deserialize, + Serialize, + BorshDeserialize, + BorshSerialize, + PartialEq, + Eq, +)] +pub struct Transactions { + pub established_account: Option>, + pub validator_account: Option>, + pub transfer: Option>, + pub bond: Option>, +} + +impl Transactions { + /// Take the union of two sets of transactions + pub fn merge(&mut self, mut other: Self) { + self.established_account = self + .established_account + .take() + .map(|mut txs| { + if let Some(new_txs) = other.established_account.as_mut() { + txs.append(new_txs); + } + txs + }) + .or(other.established_account); + self.validator_account = self + .validator_account + .take() + .map(|mut txs| { + if let Some(new_txs) = other.validator_account.as_mut() { + txs.append(new_txs); + } + txs + }) + .or(other.validator_account); + self.transfer = self + .transfer + .take() + .map(|mut txs| { + if let Some(new_txs) = other.transfer.as_mut() { + txs.append(new_txs); + } + txs + }) + .or(other.transfer); + self.bond = self + .bond + .take() + .map(|mut txs| { + if let Some(new_txs) = other.bond.as_mut() { + txs.append(new_txs); + } + txs + }) + .or(other.bond); + } +} + +impl Default for Transactions { + fn default() -> Self { + Self { + established_account: None, + validator_account: None, + transfer: None, + bond: None, + } + } +} + +impl Transactions { + /// Check that there is at least one validator. + pub fn has_at_least_one_validator(&self) -> bool { + self.validator_account + .as_ref() + .map(|txs| !txs.is_empty()) + .unwrap_or_default() + } + + /// Check if there is at least one validator with positive Tendermint voting + /// power. The voting power is converted from `token::Amount` of the + /// validator's stake using the `tm_votes_per_token` PoS parameter. + pub fn has_validator_with_positive_voting_power( + &self, + votes_per_token: Dec, + ) -> bool { + self.bond + .as_ref() + .map(|txs| { + let mut stakes: BTreeMap<&Alias, token::Amount> = + BTreeMap::new(); + for tx in txs { + let entry = stakes.entry(&tx.validator).or_default(); + *entry += tx.amount.amount; + } + + stakes.into_iter().any(|(_validator, stake)| { + let tendermint_voting_power = + namada::ledger::pos::into_tm_voting_power( + votes_per_token, + stake, + ); + if tendermint_voting_power > 0 { + return true; + } + false + }) + }) + .unwrap_or_default() + } +} + +#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)] +pub struct UnsignedTransactions { + pub established_account: Option>, + pub validator_account: Option>, + pub transfer: Option>>, + pub bond: Option>>, +} + +pub type UnsignedValidatorAccountTx = + ValidatorAccountTx>; + +pub type SignedValidatorAccountTx = ValidatorAccountTx; + +#[derive( + Clone, + Debug, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, + PartialEq, + Eq, +)] +pub struct ValidatorAccountTx { + pub alias: Alias, + pub dkg_key: StringEncoded, + pub vp: String, + /// Commission rate charged on rewards for delegators (bounded inside + /// 0-1) + pub commission_rate: Dec, + /// Maximum change in commission rate permitted per epoch + pub max_commission_rate_change: Dec, + /// P2P IP:port + pub net_address: SocketAddr, + /// PKs have to come last in TOML to avoid `ValueAfterTable` error + pub account_key: PK, + pub consensus_key: PK, + pub protocol_key: PK, + pub tendermint_node_key: PK, + pub eth_hot_key: PK, + pub eth_cold_key: PK, +} + +pub type UnsignedEstablishedAccountTx = + EstablishedAccountTx>; + +pub type SignedEstablishedAccountTx = EstablishedAccountTx; + +#[derive( + Clone, + Debug, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, + PartialEq, + Eq, +)] +pub struct EstablishedAccountTx { + pub alias: Alias, + pub vp: String, + /// PKs have to come last in TOML to avoid `ValueAfterTable` error + pub public_key: Option, + #[serde(default)] + /// Initial storage key values + pub storage: HashMap, +} + +pub type SignedTransferTx = Signed>; + +impl SignedTransferTx { + /// Verify the signature of `TransferTx`. This should not depend + /// on whether the contained amount is denominated or not. + /// + /// Since we denominate amounts as part of validation, we can + /// only verify signatures on [`SignedTransferTx`] + /// types. + pub fn verify_sig(&self) -> Result<(), VerifySigError> { + let Self { data, signature } = self; + verify_standalone_sig::<_, SerializeWithBorsh>( + &data.data_to_sign(), + &data.source.raw, + signature, + ) + } +} + +#[derive( + Clone, + Debug, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, + PartialEq, + Eq, +)] +pub struct TransferTx { + pub token: Alias, + pub source: StringEncoded, + pub target: Alias, + pub amount: T::Amount, +} + +impl TransferTx { + /// Add the correct denomination to the contained amount + pub fn denominate( + self, + tokens: &Tokens, + ) -> eyre::Result> { + let TransferTx { + token, + source, + target, + amount, + } = self; + let denom = + if let Some(super::templates::TokenConfig { denom, .. }) = + tokens.token.get(&token) + { + *denom + } else { + eprintln!( + "Genesis files contained transfer of token {}, which is \ + not in the `tokens.toml` file", + token + ); + return Err(eyre::eyre!( + "Genesis files contained transfer of token {}, which is \ + not in the `tokens.toml` file", + token + )); + }; + let amount = amount.increase_precision(denom).map_err(|e| { + eprintln!( + "A bond amount in the transactions.toml file was incorrectly \ + formatted:\n{}", + e + ); + e + })?; + + Ok(TransferTx { + token, + source, + target, + amount, + }) + } + + /// The signable data. This does not include the phantom data. + fn data_to_sign(&self) -> Vec { + [ + self.token.serialize_to_vec(), + self.source.serialize_to_vec(), + self.target.serialize_to_vec(), + self.amount.serialize_to_vec(), + ] + .concat() + } + + /// Sign the transfer. + /// + /// Since we denominate amounts as part of validation, we can + /// only verify signatures on [`SignedTransferTx`] + /// types. Thus we only allow signing of [`TransferTx`] + /// types. + pub fn sign(self, key: &common::SecretKey) -> SignedTransferTx { + let sig = standalone_signature::<_, SerializeWithBorsh>( + key, + &self.data_to_sign(), + ); + SignedTransferTx { + data: self, + signature: StringEncoded { raw: sig }, + } + } +} + +pub type SignedBondTx = Signed>; + +impl SignedBondTx { + /// Verify the signature of `BondTx`. This should not depend + /// on whether the contained amount is denominated or not. + /// + /// Since we denominate amounts as part of validation, we can + /// only verify signatures on [`SignedBondTx`] + /// types. + pub fn verify_sig( + &self, + pk: &common::PublicKey, + ) -> Result<(), VerifySigError> { + let Self { data, signature } = self; + verify_standalone_sig::<_, SerializeWithBorsh>( + &data.data_to_sign(), + pk, + signature, + ) + } +} + +#[derive( + Clone, + Debug, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, + PartialEq, + Eq, +)] +pub struct BondTx { + pub source: AliasOrPk, + pub validator: Alias, + pub amount: T::Amount, +} + +impl BondTx { + /// Add the correct denomination to the contained amount + pub fn denominate(self) -> eyre::Result> { + let BondTx { + source, + validator, + amount, + } = self; + let amount = amount + .increase_precision(NATIVE_MAX_DECIMAL_PLACES.into()) + .map_err(|e| { + eprintln!( + "A bond amount in the transactions.toml file was \ + incorrectly formatted:\n{}", + e + ); + e + })?; + Ok(BondTx { + source, + validator, + amount, + }) + } + + /// The signable data. This does not include the phantom data. + fn data_to_sign(&self) -> Vec { + [ + self.source.serialize_to_vec(), + self.validator.serialize_to_vec(), + self.amount.serialize_to_vec(), + ] + .concat() + } + + /// Sign the transfer. + /// + /// Since we denominate amounts as part of validation, we can + /// only verify signatures on [`SignedBondTx`] + /// types. Thus we only allow signing of [`BondTx`] + /// types. + pub fn sign(self, key: &common::SecretKey) -> SignedBondTx { + let sig = standalone_signature::<_, SerializeWithBorsh>( + key, + &self.data_to_sign(), + ); + SignedBondTx { + data: self, + signature: StringEncoded { raw: sig }, + } + } +} + +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq, Eq)] +pub enum AliasOrPk { + /// `alias = "value"` in toml (encoded via `AliasSerHelper`) + Alias(Alias), + /// `public_key = "value"` in toml (encoded via `PkSerHelper`) + PublicKey(StringEncoded), +} + +impl Serialize for AliasOrPk { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match self { + AliasOrPk::Alias(alias) => Serialize::serialize(alias, serializer), + AliasOrPk::PublicKey(pk) => Serialize::serialize(pk, serializer), + } + } +} + +impl<'de> Deserialize<'de> for AliasOrPk { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct FieldVisitor; + + impl<'de> serde::de::Visitor<'de> for FieldVisitor { + type Value = AliasOrPk; + + fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { + formatter.write_str( + "a bech32m encoded `common::PublicKey` or an alias", + ) + } + + fn visit_str(self, value: &str) -> Result + where + E: serde::de::Error, + { + // Try to deserialize a PK first + let maybe_pk = + StringEncoded::::from_str(value); + match maybe_pk { + Ok(pk) => Ok(AliasOrPk::PublicKey(pk)), + Err(_) => { + // If that doesn't work, use it as an alias + let alias = Alias::from_str(value) + .map_err(serde::de::Error::custom)?; + Ok(AliasOrPk::Alias(alias)) + } + } + } + } + + deserializer.deserialize_str(FieldVisitor) + } +} + +impl Display for AliasOrPk { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AliasOrPk::Alias(alias) => write!(f, "{}", alias), + AliasOrPk::PublicKey(pk) => write!(f, "{}", pk), + } + } +} + +#[derive( + Clone, + Debug, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, + PartialEq, + Eq, +)] +pub struct Signed { + #[serde(flatten)] + pub data: T, + pub signature: StringEncoded, +} + +#[derive( + Clone, + Debug, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, + PartialEq, + Eq, +)] +pub struct SignedPk { + pub pk: StringEncoded, + pub authorization: StringEncoded, +} + +pub fn validate( + transactions: Transactions, + vps: Option<&ValidityPredicates>, + balances: Option<&DenominatedBalances>, + tokens: &Tokens, + parameters: Option<&Parameters>, +) -> Option> { + let mut is_valid = true; + + let mut all_used_aliases: BTreeSet = BTreeSet::default(); + let mut established_accounts: BTreeMap> = + BTreeMap::default(); + let mut validator_accounts: BTreeMap = + BTreeMap::default(); + + let Transactions { + ref established_account, + ref validator_account, + ref transfer, + bond, + } = transactions; + + if let Some(txs) = established_account { + for tx in txs { + if !validate_established_account( + tx, + vps, + &mut all_used_aliases, + &mut established_accounts, + ) { + is_valid = false; + } + } + } + + if let Some(txs) = validator_account { + for tx in txs { + if !validate_validator_account( + tx, + vps, + &mut all_used_aliases, + &mut validator_accounts, + ) { + is_valid = false; + } + } + } + + // Make a mutable copy of the balances for tracking changes applied from txs + let mut token_balances: BTreeMap = + balances + .map(|balances| { + balances + .token + .iter() + .map(|(token, token_balances)| { + ( + token.clone(), + TokenBalancesForValidation { + // Add an accumulator for tokens transferred to + // aliases + aliases: BTreeMap::new(), + pks: token_balances.clone(), + }, + ) + }) + .collect() + }) + .unwrap_or_default(); + + let validated_txs = if let Some(txs) = transfer { + let validated_txs: Vec<_> = txs + .iter() + .filter_map(|tx| { + validate_transfer( + tx, + &mut token_balances, + &all_used_aliases, + tokens, + ) + }) + .collect(); + if validated_txs.len() != txs.len() { + is_valid = false; + None + } else { + Some(validated_txs) + } + } else { + None + }; + + let validated_bonds = if let Some(txs) = bond { + if !txs.is_empty() { + match parameters { + Some(parameters) => { + let bond_number = txs.len(); + let validated_bonds: Vec<_> = txs + .into_iter() + .filter_map(|tx| { + validate_bond( + tx, + &mut token_balances, + &established_accounts, + &validator_accounts, + parameters, + ) + }) + .collect(); + if validated_bonds.len() != bond_number { + is_valid = false; + None + } else { + Some(validated_bonds) + } + } + None => { + eprintln!( + "Unable to validate bonds without a valid parameters \ + file." + ); + is_valid = false; + None + } + } + } else { + None + } + } else { + None + }; + + is_valid.then_some(Transactions { + established_account: transactions.established_account, + validator_account: transactions.validator_account, + transfer: validated_txs, + bond: validated_bonds, + }) +} + +fn validate_bond( + tx: SignedBondTx, + balances: &mut BTreeMap, + established_accounts: &BTreeMap>, + validator_accounts: &BTreeMap, + parameters: &Parameters, +) -> Option> { + // Check signature + let mut is_valid = { + let source = &tx.data.source; + if let Some(source_pk) = match source { + AliasOrPk::Alias(alias) => { + // Try to find the source's PK in either established_accounts or + // validator_accounts + established_accounts + .get(alias) + .cloned() + .flatten() + .or_else(|| validator_accounts.get(alias).cloned()) + } + AliasOrPk::PublicKey(pk) => Some(pk.raw.clone()), + } { + if tx.verify_sig(&source_pk).is_err() { + eprintln!("Invalid bond tx signature.",); + false + } else { + true + } + } else { + eprintln!( + "Invalid bond tx. Couldn't verify bond's signature, because \ + the source accounts \"{source}\" public key cannot be found." + ); + false + } + }; + + // Make sure the native token amount is denominated correctly + let validated_bond = tx.data.denominate().ok()?; + let BondTx { + source, + validator, + amount, + .. + } = &validated_bond; + + // Check that the validator exists + if !validator_accounts.contains_key(validator) { + eprintln!( + "Invalid bond tx. The target validator \"{validator}\" account \ + not found." + ); + is_valid = false; + } + + // Check and update token balance of the source + let native_token = ¶meters.parameters.native_token; + match balances.get_mut(native_token) { + Some(balances) => { + let balance = match source { + AliasOrPk::Alias(source) => balances.aliases.get_mut(source), + AliasOrPk::PublicKey(source) => balances.pks.0.get_mut(source), + }; + match balance { + Some(balance) => { + if *balance < *amount { + eprintln!( + "Invalid bond tx. Source {source} doesn't have \ + enough balance of token \"{native_token}\" to \ + transfer {}. Got {}.", + amount, balance, + ); + is_valid = false; + } else { + // Deduct the amount from source + if amount == balance { + match source { + AliasOrPk::Alias(source) => { + balances.aliases.remove(source); + } + AliasOrPk::PublicKey(source) => { + balances.pks.0.remove(source); + } + } + } else { + balance.amount -= amount.amount; + } + } + } + None => { + eprintln!( + "Invalid transfer tx. Source {source} has no balance \ + of token \"{native_token}\"." + ); + is_valid = false; + } + } + } + None => { + eprintln!( + "Invalid bond tx. Token \"{native_token}\" not found in \ + balances." + ); + is_valid = false; + } + } + + is_valid.then_some(validated_bond) +} + +#[derive(Clone, Debug)] +pub struct TokenBalancesForValidation { + /// Accumulator for tokens transferred to aliases + pub aliases: BTreeMap, + /// Token balances from the balances file, associated with PKs + pub pks: TokenBalances, +} + +pub fn validate_established_account( + tx: &SignedEstablishedAccountTx, + vps: Option<&ValidityPredicates>, + all_used_aliases: &mut BTreeSet, + established_accounts: &mut BTreeMap>, +) -> bool { + let mut is_valid = true; + + established_accounts.insert( + tx.alias.clone(), + tx.public_key.as_ref().map(|signed| signed.pk.raw.clone()), + ); + + // Check that alias is unique + if all_used_aliases.contains(&tx.alias) { + eprintln!( + "A duplicate alias \"{}\" found in a `established_account` tx.", + tx.alias + ); + is_valid = false; + } else { + all_used_aliases.insert(tx.alias.clone()); + } + + // Check the VP exists + if !vps + .map(|vps| vps.wasm.contains_key(&tx.vp)) + .unwrap_or_default() + { + eprintln!( + "An `established_account` tx `vp` \"{}\" not found in Validity \ + predicates file.", + tx.vp + ); + is_valid = false; + } + + // If PK is used, check the authorization + if let Some(pk) = tx.public_key.as_ref() { + if !validate_established_account_sig(pk, tx) { + is_valid = false; + } + } + + is_valid +} + +fn validate_established_account_sig( + SignedPk { pk, authorization }: &SignedPk, + tx: &SignedEstablishedAccountTx, +) -> bool { + let unsigned = UnsignedEstablishedAccountTx::from(tx); + validate_signature(&unsigned, &pk.raw, &authorization.raw) +} + +pub fn validate_validator_account( + tx: &ValidatorAccountTx, + vps: Option<&ValidityPredicates>, + all_used_aliases: &mut BTreeSet, + validator_accounts: &mut BTreeMap, +) -> bool { + let mut is_valid = true; + + validator_accounts.insert(tx.alias.clone(), tx.account_key.pk.raw.clone()); + + // Check that alias is unique + if all_used_aliases.contains(&tx.alias) { + eprintln!( + "A duplicate alias \"{}\" found in a `validator_account` tx.", + tx.alias + ); + is_valid = false; + } else { + all_used_aliases.insert(tx.alias.clone()); + } + + // Check the VP exists + if !vps + .map(|vps| vps.wasm.contains_key(&tx.vp)) + .unwrap_or_default() + { + eprintln!( + "A `validator_account` tx `vp` \"{}\" not found in Validity \ + predicates file.", + tx.vp + ); + is_valid = false; + } + + // Check keys authorizations + let unsigned = UnsignedValidatorAccountTx::from(tx); + if !validate_signature( + &unsigned, + &tx.account_key.pk.raw, + &tx.account_key.authorization.raw, + ) { + eprintln!( + "Invalid `account_key` authorization for `validator_account` tx \ + with alias \"{}\".", + tx.alias + ); + is_valid = false; + } + if !validate_signature( + &unsigned, + &tx.consensus_key.pk.raw, + &tx.consensus_key.authorization.raw, + ) { + eprintln!( + "Invalid `consensus_key` authorization for `validator_account` tx \ + with alias \"{}\".", + tx.alias + ); + is_valid = false; + } + if !validate_signature( + &unsigned, + &tx.protocol_key.pk.raw, + &tx.protocol_key.authorization.raw, + ) { + eprintln!( + "Invalid `protocol_key` authorization for `validator_account` tx \ + with alias \"{}\".", + tx.alias + ); + is_valid = false; + } + if !validate_signature( + &unsigned, + &tx.tendermint_node_key.pk.raw, + &tx.tendermint_node_key.authorization.raw, + ) { + eprintln!( + "Invalid `tendermint_node_key` authorization for \ + `validator_account` tx with alias \"{}\".", + tx.alias + ); + is_valid = false; + } + + if !validate_signature( + &unsigned, + &tx.eth_hot_key.pk.raw, + &tx.eth_hot_key.authorization.raw, + ) { + eprintln!( + "Invalid `eth_hot_key` authorization for `validator_account` tx \ + with alias \"{}\".", + tx.alias + ); + is_valid = false; + } + + if !validate_signature( + &unsigned, + &tx.eth_cold_key.pk.raw, + &tx.eth_cold_key.authorization.raw, + ) { + eprintln!( + "Invalid `eth_cold_key` authorization for `validator_account` tx \ + with alias \"{}\".", + tx.alias + ); + is_valid = false; + } + + is_valid +} + +/// Updates the token balances with all the valid transfers applied +pub fn validate_transfer( + tx: &SignedTransferTx, + balances: &mut BTreeMap, + all_used_aliases: &BTreeSet, + tokens: &Tokens, +) -> Option> { + let mut is_valid = true; + // Check signature + if tx.verify_sig().is_err() { + eprintln!("Invalid transfer tx signature.",); + is_valid = false; + } + + let unsigned: TransferTx = tx.into(); + let validated = unsigned.denominate(tokens).ok()?; + let TransferTx { + token, + source, + target, + amount, + .. + } = &validated; + + // Check that the target exists + if !all_used_aliases.contains(target) { + eprintln!( + "Invalid transfer tx. The target alias \"{target}\" no matching \ + account found." + ); + is_valid = false; + } + + // Check token balance of the source and update token balances of the source + // and target + match balances.get_mut(token) { + Some(balances) => match balances.pks.0.get_mut(source) { + Some(balance) => { + if balance.amount < amount.amount { + eprintln!( + "Invalid transfer tx. Source {source} doesn't have \ + enough balance of token \"{token}\" to transfer {}. \ + Got {}.", + amount, balance, + ); + is_valid = false; + } else { + // Deduct the amount from source + if amount.amount == balance.amount { + balances.pks.0.remove(source); + } else { + balance.amount -= amount.amount; + } + + // Add the amount to target + let target_balance = balances + .aliases + .entry(target.clone()) + .or_insert_with(|| DenominatedAmount { + amount: token::Amount::zero(), + denom: amount.denom, + }); + target_balance.amount += amount.amount; + } + } + None => { + eprintln!( + "Invalid transfer tx. Source {source} has no balance of \ + token \"{token}\"." + ); + is_valid = false; + } + }, + None => { + eprintln!( + "Invalid transfer tx. Token \"{token}\" not found in balances." + ); + is_valid = false; + } + } + + is_valid.then_some(validated) +} + +fn validate_signature( + tx_data: &T, + pk: &common::PublicKey, + sig: &common::Signature, +) -> bool { + match verify_standalone_sig::(tx_data, pk, sig) { + Ok(()) => true, + Err(err) => { + eprintln!( + "Invalid tx signature in tx {tx_data:?}, failed with: {err}." + ); + false + } + } +} + +impl From<&SignedEstablishedAccountTx> for UnsignedEstablishedAccountTx { + fn from(tx: &SignedEstablishedAccountTx) -> Self { + let SignedEstablishedAccountTx { + alias, + vp, + public_key, + storage, + } = tx; + Self { + alias: alias.clone(), + vp: vp.clone(), + public_key: public_key.as_ref().map(|signed| signed.pk.clone()), + storage: storage.clone(), + } + } +} + +impl From<&SignedValidatorAccountTx> for UnsignedValidatorAccountTx { + fn from(tx: &SignedValidatorAccountTx) -> Self { + let SignedValidatorAccountTx { + alias, + dkg_key, + vp, + commission_rate, + max_commission_rate_change, + net_address, + account_key, + consensus_key, + protocol_key, + tendermint_node_key, + eth_hot_key, + eth_cold_key, + } = tx; + + Self { + alias: alias.clone(), + dkg_key: dkg_key.clone(), + vp: vp.clone(), + commission_rate: *commission_rate, + max_commission_rate_change: *max_commission_rate_change, + net_address: *net_address, + account_key: account_key.pk.clone(), + consensus_key: consensus_key.pk.clone(), + protocol_key: protocol_key.pk.clone(), + tendermint_node_key: tendermint_node_key.pk.clone(), + eth_hot_key: eth_hot_key.pk.clone(), + eth_cold_key: eth_cold_key.pk.clone(), + } + } +} + +impl From<&SignedTransferTx> for TransferTx { + fn from(tx: &SignedTransferTx) -> Self { + let SignedTransferTx { data, signature: _ } = tx; + data.clone() + } +} + +impl From<&SignedBondTx> for BondTx { + fn from(tx: &SignedBondTx) -> Self { + let SignedBondTx { data, signature: _ } = tx; + data.clone() + } +} diff --git a/apps/src/lib/config/global.rs b/apps/src/lib/config/global.rs index f1d8e5d483..1dc1380635 100644 --- a/apps/src/lib/config/global.rs +++ b/apps/src/lib/config/global.rs @@ -14,8 +14,6 @@ pub const FILENAME: &str = "global-config.toml"; pub enum Error { #[error("Error while reading config: {0}")] ReadError(config::ConfigError), - #[error("Error while reading config: {0}")] - FileNotFound(String), #[error("Error while deserializing config: {0}")] DeserializationError(config::ConfigError), #[error("Error while writing config: {0}")] @@ -26,29 +24,31 @@ pub enum Error { pub type Result = std::result::Result; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Default, Serialize, Deserialize)] pub struct GlobalConfig { /// The default chain ID - pub default_chain_id: ChainId, + pub default_chain_id: Option, // NOTE: There will be sub-chains in here in future } impl GlobalConfig { pub fn new(default_chain_id: ChainId) -> Self { - Self { default_chain_id } + Self { + default_chain_id: Some(default_chain_id), + } } - /// Try to read the global config from a file. + /// Try to read the global config from a file. Returns a config without + /// a `default_chain_id` if none exists. pub fn read(base_dir: impl AsRef) -> Result { let file_path = Self::file_path(base_dir.as_ref()); let file_name = file_path.to_str().expect("Expected UTF-8 file path"); - if !file_path.exists() { - return Err(Error::FileNotFound(file_name.to_string())); - }; let mut config = config::Config::new(); - config - .merge(config::File::with_name(file_name)) - .map_err(Error::ReadError)?; + if file_path.exists() { + config + .merge(config::File::with_name(file_name)) + .map_err(Error::ReadError)?; + } config.try_into().map_err(Error::DeserializationError) } diff --git a/apps/src/lib/config/mod.rs b/apps/src/lib/config/mod.rs index f6307c6bd1..735391d056 100644 --- a/apps/src/lib/config/mod.rs +++ b/apps/src/lib/config/mod.rs @@ -41,7 +41,7 @@ pub struct Config { pub ledger: Ledger, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum TendermintMode { Full, Validator, diff --git a/apps/src/lib/config/utils.rs b/apps/src/lib/config/utils.rs index 03725d112c..41658a000c 100644 --- a/apps/src/lib/config/utils.rs +++ b/apps/src/lib/config/utils.rs @@ -64,6 +64,30 @@ pub fn convert_tm_addr_to_socket_addr( } } +/// Set the IP address of a [`TendermintAddress`] +pub fn set_ip(tm_addr: &mut TendermintAddress, new_host: impl Into) { + match tm_addr { + TendermintAddress::Tcp { host, .. } => { + *host = new_host.into(); + } + TendermintAddress::Unix { path: _ } => { + panic!("Unix addresses aren't currently supported.") + } + } +} + +/// Set the port of a [`TendermintAddress`] +pub fn set_port(tm_addr: &mut TendermintAddress, new_port: impl Into) { + match tm_addr { + TendermintAddress::Tcp { port, .. } => { + *port = new_port.into(); + } + TendermintAddress::Unix { path: _ } => { + panic!("Unix addresses aren't currently supported.") + } + } +} + #[cfg(test)] mod test { use std::panic; diff --git a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs index 371ecf4a92..64a05b64ff 100644 --- a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs @@ -327,6 +327,7 @@ pub(crate) enum ProcessEventAction { impl ProcessEventAction { /// Check whether the action commands a new block to be processed. #[inline] + #[allow(dead_code)] pub fn process_new_block(&self) -> bool { matches!(self, Self::ProceedToNextBlock) } diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 3e1dbe0b46..5d4d1845a9 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -33,7 +33,6 @@ use crate::config::{ethereum_bridge, TendermintMode}; use crate::facade::tendermint_proto::abci::CheckTxType; use crate::facade::tower_abci::{response, split, Server}; use crate::node::ledger::broadcaster::Broadcaster; -use crate::node::ledger::config::genesis; use crate::node::ledger::ethereum_oracle as oracle; use crate::node::ledger::shell::{Error, MempoolTxType, Shell}; use crate::node::ledger::shims::abcipp_shim::AbcippShim; @@ -98,7 +97,7 @@ impl Shell { tracing::debug!("Request InitChain"); self.init_chain( init, - #[cfg(any(test, feature = "dev"))] + #[cfg(any(test, feature = "testing"))] 1, ) .map(Response::InitChain) @@ -469,10 +468,7 @@ fn start_abci_broadcaster_shell( let tendermint_mode = config.shell.tendermint_mode.clone(); let proxy_app_address = convert_tm_addr_to_socket_addr(&config.cometbft.proxy_app); - #[cfg(not(any(test, feature = "dev")))] - let genesis = genesis::genesis(&config.shell.base_dir, &config.chain_id); - #[cfg(any(test, feature = "dev"))] - let genesis = genesis::genesis(1); + let (shell, abci_service, service_handle) = AbcippShim::new( config, wasm_dir, @@ -481,7 +477,6 @@ fn start_abci_broadcaster_shell( &db_cache, vp_wasm_compilation_cache, tx_wasm_compilation_cache, - genesis.native_token, ); // Channel for signalling shut down to ABCI server diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 4fef1fc7ef..2202f7a157 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -1667,7 +1667,6 @@ mod test_finalize_block { /// Test that adding a new erc20 transfer to the bridge pool /// increments the pool's nonce. fn test_bp_nonce_is_incremented() { - use crate::node::ledger::shell::address::nam; test_bp(|shell: &mut TestShell| { let asset = EthAddress([0xff; 20]); let receiver = EthAddress([0xaa; 20]); @@ -1694,7 +1693,7 @@ mod test_finalize_block { { let amt: Amount = 999_999_u64.into(); let pool_balance_key = token::balance_key( - &nam(), + &shell.wl_storage.storage.native_token, &bridge_pool::BRIDGE_POOL_ADDRESS, ); shell @@ -1717,7 +1716,7 @@ mod test_finalize_block { sender: bertha.clone(), }, gas_fee: GasFee { - token: nam(), + token: shell.wl_storage.storage.native_token.clone(), amount: 10u64.into(), payer: bertha.clone(), }, @@ -3090,11 +3089,12 @@ mod test_finalize_block { &val1.address, del_1_amount, current_epoch, + None, ) .unwrap(); // Self-unbond - let self_unbond_1_amount = token::Amount::native_whole(154_654); + let self_unbond_1_amount = token::Amount::native_whole(54_654); namada_proof_of_stake::unbond_tokens( &mut shell.wl_storage, None, @@ -3190,6 +3190,7 @@ mod test_finalize_block { &val1.address, self_bond_1_amount, current_epoch, + None, ) .unwrap(); @@ -3231,6 +3232,7 @@ mod test_finalize_block { &val1.address, del_2_amount, current_epoch, + None, ) .unwrap(); diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 2554349680..57ddde6b26 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -2,23 +2,30 @@ use std::collections::HashMap; use std::hash::Hash; -use namada::ledger::parameters::{self, Parameters}; -use namada::ledger::pos::{staking_token_address, OwnedPosParams}; +use namada::ledger::parameters::Parameters; use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::{DBIter, DB}; -use namada::ledger::storage_api::token::{ - credit_tokens, read_balance, read_total_supply, write_denom, -}; -use namada::ledger::storage_api::{ResultExt, StorageRead, StorageWrite}; +use namada::ledger::storage_api::token::{credit_tokens, write_denom}; +use namada::ledger::storage_api::StorageWrite; use namada::ledger::{ibc, pos}; -use namada::types::dec::Dec; +use namada::proof_of_stake::BecomeValidator; use namada::types::hash::Hash as CodeHash; use namada::types::key::*; +use namada::types::storage::KeySeg; use namada::types::time::{DateTimeUtc, TimeZone, Utc}; use namada::vm::validate_untrusted_wasm; use namada_sdk::eth_bridge::EthBridgeStatus; +use namada_sdk::proof_of_stake::PosParams; use super::*; +use crate::config::genesis::chain::{ + FinalizedEstablishedAccountTx, FinalizedTokenConfig, + FinalizedValidatorAccountTx, +}; +use crate::config::genesis::templates::{TokenBalances, TokenConfig}; +use crate::config::genesis::transactions::{ + BondTx, EstablishedAccountTx, TransferTx, ValidatorAccountTx, +}; use crate::facade::tendermint_proto::google::protobuf; use crate::facade::tower_abci::{request, response}; use crate::wasm_loader; @@ -39,32 +46,44 @@ where pub fn init_chain( &mut self, init: request::InitChain, - #[cfg(any(test, feature = "dev"))] num_validators: u64, + #[cfg(any(test, feature = "testing"))] _num_validators: u64, ) -> Result { - let (current_chain_id, _) = self.wl_storage.storage.get_chain_id(); - if current_chain_id != init.chain_id { + let mut response = response::InitChain::default(); + let chain_id = self.wl_storage.storage.chain_id.as_str(); + if chain_id != init.chain_id.as_str() { return Err(Error::ChainId(format!( "Current chain ID: {}, Tendermint chain ID: {}", - current_chain_id, init.chain_id + chain_id, init.chain_id ))); } - #[cfg(not(any(test, feature = "dev")))] - let genesis = - genesis::genesis(&self.base_dir, &self.wl_storage.storage.chain_id); - #[cfg(not(any(test, feature = "dev")))] + + // Read the genesis files + #[cfg(any( + feature = "integration", + not(any(test, feature = "benches")) + ))] + let genesis = { + let chain_dir = self.base_dir.join(chain_id); + genesis::chain::Finalized::read_toml_files(&chain_dir) + .expect("Missing genesis files") + }; + #[cfg(all( + any(test, feature = "benches"), + not(feature = "integration") + ))] + let genesis = { + let chain_dir = self.base_dir.join(chain_id); + genesis::make_dev_genesis(_num_validators, chain_dir) + }; + #[cfg(all( + any(test, feature = "benches"), + not(feature = "integration") + ))] { - let genesis_bytes = genesis.serialize_to_vec(); - let errors = - self.wl_storage.storage.chain_id.validate(genesis_bytes); - use itertools::Itertools; - assert!( - errors.is_empty(), - "Chain ID validation failed: {}", - errors.into_iter().format(". ") - ); + // update the native token from the genesis file + let native_token = genesis.get_native_token().clone(); + self.wl_storage.storage.native_token = native_token; } - #[cfg(any(test, feature = "dev"))] - let genesis = genesis::genesis(num_validators); let ts: protobuf::Timestamp = init.time.expect("Missing genesis time"); let initial_height = init @@ -79,26 +98,112 @@ where .into(); // Initialize protocol parameters - let genesis::Parameters { - epoch_duration, - max_proposal_bytes, - max_block_gas, - max_expected_time_per_block, - vp_whitelist, + let parameters = genesis.get_chain_parameters(&self.wasm_dir); + self.store_wasms(¶meters)?; + parameters.init_storage(&mut self.wl_storage)?; + + // Initialize governance parameters + let gov_params = genesis.get_gov_params(); + gov_params.init_storage(&mut self.wl_storage)?; + + // configure the Ethereum bridge if the configuration is set. + if let Some(config) = genesis.get_eth_bridge_params() { + tracing::debug!("Initializing Ethereum bridge storage."); + config.init_storage(&mut self.wl_storage); + self.update_eth_oracle(); + } else { + self.wl_storage + .write_bytes( + &namada::eth_bridge::storage::active_key(), + EthBridgeStatus::Disabled.serialize_to_vec(), + ) + .unwrap(); + } + + // Depends on parameters being initialized + self.wl_storage + .storage + .init_genesis_epoch(initial_height, genesis_time, ¶meters) + .expect("Initializing genesis epoch must not fail"); + + // PoS system depends on epoch being initialized + let pos_params = genesis.get_pos_params(); + let (current_epoch, _gas) = self.wl_storage.storage.get_current_epoch(); + pos::namada_proof_of_stake::init_genesis( + &mut self.wl_storage, + &pos_params, + current_epoch, + ) + .expect("Must be able to initialize PoS genesis storage"); + + // PGF parameters + let pgf_params = genesis.get_pgf_params(); + pgf_params + .init_storage(&mut self.wl_storage) + .expect("Should be able to initialized PGF at genesis"); + + // Loaded VP code cache to avoid loading the same files multiple times + let mut vp_cache: HashMap> = HashMap::default(); + self.init_token_accounts(&genesis); + self.init_token_balances(&genesis); + self.apply_genesis_txs_established_account(&genesis, &mut vp_cache); + self.apply_genesis_txs_validator_account( + &genesis, + &mut vp_cache, + &pos_params, + current_epoch, + ); + self.apply_genesis_txs_transfer(&genesis); + self.apply_genesis_txs_bonds(&genesis); + + pos::namada_proof_of_stake::store_total_consensus_stake( + &mut self.wl_storage, + Default::default(), + ) + .expect("Could not compute total consensus stake at genesis"); + // This has to be done after `apply_genesis_txs_validator_account` + pos::namada_proof_of_stake::copy_genesis_validator_sets( + &mut self.wl_storage, + &pos_params, + current_epoch, + ) + .expect("Must be able to copy PoS genesis validator sets"); + + ibc::init_genesis_storage(&mut self.wl_storage); + + // Set the initial validator set + response.validators = self + .get_abci_validator_updates(true) + .expect("Must be able to set genesis validator set"); + debug_assert!(!response.validators.is_empty()); + Ok(response) + } + + /// Look-up WASM code of a genesis VP by its name + fn lookup_vp( + &self, + name: &str, + genesis: &genesis::chain::Finalized, + vp_cache: &mut HashMap>, + ) -> Vec { + let config = + genesis.vps.wasm.get(name).unwrap_or_else(|| { + panic!("Missing validity predicate for {name}") + }); + let vp_filename = &config.filename; + vp_cache.get_or_insert_with(vp_filename.clone(), || { + wasm_loader::read_wasm(&self.wasm_dir, vp_filename).unwrap() + }) + } + + fn store_wasms(&mut self, params: &Parameters) -> Result<()> { + let Parameters { tx_whitelist, - implicit_vp_code_path, - implicit_vp_sha256, - epochs_per_year, - max_signatures_per_transaction, - pos_gain_p, - pos_gain_d, - staked_ratio, - pos_inflation_amount, - minimum_gas_price, - fee_unshielding_gas_limit, - fee_unshielding_descriptions_limit, - } = genesis.parameters; - // Store wasm codes into storage + vp_whitelist, + implicit_vp_code_hash, + .. + } = params; + let mut is_implicit_vp_stored = false; let checksums = wasm_loader::Checksums::read_checksums(&self.wasm_dir); for (name, full_name) in checksums.0.iter() { let code = wasm_loader::read_wasm(&self.wasm_dir, name) @@ -140,6 +245,9 @@ where self.wl_storage.write_bytes(&code_key, code)?; self.wl_storage.write(&code_len_key, code_len)?; self.wl_storage.write_bytes(&hash_key, code_hash)?; + if &code_hash == implicit_vp_code_hash { + is_implicit_vp_stored = true; + } self.wl_storage.write_bytes(&code_name_key, code_hash)?; } else { tracing::warn!("The wasm {name} isn't whitelisted."); @@ -147,353 +255,357 @@ where } // check if implicit_vp wasm is stored - let implicit_vp_code_hash = - read_wasm_hash(&self.wl_storage, &implicit_vp_code_path)?.ok_or( - Error::LoadingWasm(format!( - "Unknown vp code path: {}", - implicit_vp_code_path - )), - )?; - // In dev, we don't check the hash - #[cfg(feature = "dev")] - let _ = implicit_vp_sha256; - #[cfg(not(feature = "dev"))] - { - assert_eq!( - implicit_vp_code_hash.0.as_slice(), - &implicit_vp_sha256, - "Invalid implicit account's VP sha256 hash for {}", - implicit_vp_code_path - ); - } + assert!( + is_implicit_vp_stored, + "No VP found matching the expected implicit VP sha256 hash: {}", + implicit_vp_code_hash + ); + Ok(()) + } - let parameters = Parameters { - epoch_duration, - max_proposal_bytes, - max_block_gas, - max_expected_time_per_block, - vp_whitelist, - tx_whitelist, - implicit_vp_code_hash, - epochs_per_year, - max_signatures_per_transaction, - pos_gain_p, - pos_gain_d, - staked_ratio, - pos_inflation_amount, - minimum_gas_price, - fee_unshielding_gas_limit, - fee_unshielding_descriptions_limit, - }; - parameters - .init_storage(&mut self.wl_storage) - .expect("Initializing chain parameters must not fail"); + /// Init genesis token accounts + fn init_token_accounts(&mut self, genesis: &genesis::chain::Finalized) { + let masp_rewards = address::tokens(); + for (alias, token) in &genesis.tokens.token { + tracing::debug!("Initializing token {alias}"); - // Initialize governance parameters - genesis - .gov_params - .init_storage(&mut self.wl_storage) - .expect("Initializing chain parameters must not fail"); - - // Initialize pgf parameters - genesis - .pgf_params - .init_storage(&mut self.wl_storage) - .expect("Initializing chain parameters must not fail"); + let FinalizedTokenConfig { + address, + config: TokenConfig { denom, parameters }, + } = token; + // associate a token with its denomination. + write_denom(&mut self.wl_storage, address, *denom).unwrap(); + parameters.init_storage(address, &mut self.wl_storage); + // add token addresses to the masp reward conversions lookup table. + let alias = alias.to_string(); + if masp_rewards.contains_key(&alias.as_str()) { + self.wl_storage + .storage + .conversion_state + .tokens + .insert(alias, address.clone()); + } + } + let key_prefix: Key = address::masp().to_db_key().into(); + // Save the current conversion state + let state_key = key_prefix + .push(&(token::CONVERSION_KEY_PREFIX.to_owned())) + .unwrap(); + let conv_bytes = + self.wl_storage.storage.conversion_state.serialize_to_vec(); + self.wl_storage.write_bytes(&state_key, conv_bytes).unwrap(); + } - // configure the Ethereum bridge if the configuration is set. - if let Some(config) = genesis.ethereum_bridge_params { - tracing::debug!("Initializing Ethereum bridge storage."); - config.init_storage(&mut self.wl_storage); - self.update_eth_oracle(); - } else { + /// Init genesis token balances + fn init_token_balances(&mut self, genesis: &genesis::chain::Finalized) { + for (token_alias, TokenBalances(balances)) in &genesis.balances.token { + tracing::debug!("Initializing token balances {token_alias}"); + + let token_address = &genesis + .tokens + .token + .get(token_alias) + .expect("Token with configured balance not found in genesis.") + .address; + let mut total_token_balance = token::Amount::zero(); + for (owner_pk, balance) in balances { + let owner = Address::from(&owner_pk.raw); + storage_api::account::set_public_key_at( + &mut self.wl_storage, + &owner, + &owner_pk.raw, + 0, + ) + .unwrap(); + tracing::info!( + "Crediting {} {} tokens to {}", + balance, + token_alias, + owner_pk.raw + ); + credit_tokens( + &mut self.wl_storage, + token_address, + &owner, + balance.amount, + ) + .expect("Couldn't credit initial balance"); + total_token_balance += balance.amount; + } + // Write the total amount of tokens for the ratio self.wl_storage - .write_bytes( - &namada::eth_bridge::storage::active_key(), - EthBridgeStatus::Disabled.serialize_to_vec(), + .write( + &token::minted_balance_key(token_address), + total_token_balance, ) .unwrap(); } - - // Depends on parameters being initialized - self.wl_storage - .storage - .init_genesis_epoch(initial_height, genesis_time, ¶meters) - .expect("Initializing genesis epoch must not fail"); - - // Initialize genesis established accounts - self.initialize_established_accounts( - genesis.established_accounts, - &implicit_vp_code_path, - )?; - - // Initialize genesis implicit - self.initialize_implicit_accounts(genesis.implicit_accounts); - - // Initialize genesis token accounts - self.initialize_token_accounts(genesis.token_accounts); - - // Initialize genesis validator accounts - let staking_token = staking_token_address(&self.wl_storage); - self.initialize_validators( - &staking_token, - &genesis.validators, - &implicit_vp_code_path, - ); - // set the initial validators set - self.set_initial_validators( - &staking_token, - genesis.validators, - &genesis.pos_params, - ) } - /// Initialize genesis established accounts - fn initialize_established_accounts( + /// Apply genesis txs to initialize established accounts + fn apply_genesis_txs_established_account( &mut self, - accounts: Vec, - implicit_vp_code_path: &str, - ) -> Result<()> { - for genesis::EstablishedAccount { - address, - vp_code_path, - vp_sha256, - public_key, - storage, - } in accounts - { - let vp_code_hash = read_wasm_hash(&self.wl_storage, &vp_code_path)? - .ok_or(Error::LoadingWasm(format!( - "Unknown vp code path: {}", - implicit_vp_code_path - )))?; - - // In dev, we don't check the hash - #[cfg(feature = "dev")] - let _ = vp_sha256; - #[cfg(not(feature = "dev"))] + genesis: &genesis::chain::Finalized, + vp_cache: &mut HashMap>, + ) { + let vp_code = self.lookup_vp("vp_masp", genesis, vp_cache); + let code_hash = CodeHash::sha256(&vp_code); + self.wl_storage + .write_bytes(&Key::validity_predicate(&address::masp()), code_hash) + .unwrap(); + if let Some(txs) = genesis.transactions.established_account.as_ref() { + for FinalizedEstablishedAccountTx { + address, + tx: + EstablishedAccountTx { + alias, + vp, + public_key, + storage, + }, + } in txs { - assert_eq!( - vp_code_hash.0.as_slice(), - &vp_sha256, - "Invalid established account's VP sha256 hash for {}", - vp_code_path + tracing::debug!( + "Applying genesis tx to init an established account \ + {alias}" ); - } - - self.wl_storage - .write_bytes(&Key::validity_predicate(&address), vp_code_hash) - .unwrap(); + let vp_code = self.lookup_vp(vp, genesis, vp_cache); + let code_hash = CodeHash::sha256(&vp_code); + self.wl_storage + .write_bytes(&Key::validity_predicate(address), code_hash) + .unwrap(); - if let Some(pk) = public_key { - storage_api::account::set_public_key_at( - &mut self.wl_storage, - &address, - &pk, - 0, - )?; - } + if let Some(pk) = public_key { + storage_api::account::set_public_key_at( + &mut self.wl_storage, + address, + &pk.pk.raw, + 0, + ) + .unwrap(); + } - for (key, value) in storage { - self.wl_storage.write_bytes(&key, value).unwrap(); + // Place the keys under the owners sub-storage + let sub_key = namada::core::types::storage::Key::from( + address.to_db_key(), + ); + for (key, value) in storage { + self.wl_storage + .write_bytes(&sub_key.join(key), value.parse().unwrap()) + .unwrap(); + } } } - - Ok(()) } - /// Initialize genesis implicit accounts - fn initialize_implicit_accounts( + /// Apply genesis txs to initialize validator accounts + fn apply_genesis_txs_validator_account( &mut self, - accounts: Vec, + genesis: &genesis::chain::Finalized, + vp_cache: &mut HashMap>, + params: &PosParams, + current_epoch: namada::types::storage::Epoch, ) { - // Initialize genesis implicit - for genesis::ImplicitAccount { public_key } in accounts { - let address: address::Address = (&public_key).into(); - storage_api::account::set_public_key_at( - &mut self.wl_storage, - &address, - &public_key, - 0, - ) - .unwrap(); - } - } + if let Some(txs) = genesis.transactions.validator_account.as_ref() { + for FinalizedValidatorAccountTx { + address, + tx: + ValidatorAccountTx { + alias, + vp, + dkg_key, + commission_rate, + max_commission_rate_change, + net_address: _, + account_key, + consensus_key, + protocol_key, + tendermint_node_key: _, + eth_hot_key, + eth_cold_key, + }, + } in txs + { + tracing::debug!( + "Applying genesis tx to init a validator account {alias}" + ); - /// Initialize genesis token accounts - fn initialize_token_accounts( - &mut self, - accounts: Vec, - ) { - // Initialize genesis token accounts - for genesis::TokenAccount { - address, - denom, - balances, - parameters, - last_inflation, - last_locked_ratio, - } in accounts - { - // Init token parameters and last inflation and caching rates - parameters.init_storage(&address, &mut self.wl_storage); - self.wl_storage - .write( - &token::masp_last_inflation_key(&address), - last_inflation, - ) - .unwrap(); - self.wl_storage - .write( - &token::masp_last_locked_ratio_key(&address), - last_locked_ratio, + let vp_code = self.lookup_vp(vp, genesis, vp_cache); + let code_hash = CodeHash::sha256(&vp_code); + self.wl_storage + .write_bytes(&Key::validity_predicate(address), code_hash) + .expect("Unable to write user VP"); + // Validator account key + storage_api::account::set_public_key_at( + &mut self.wl_storage, + address, + &account_key.pk.raw, + 0, ) .unwrap(); - // associate a token with its denomination. - write_denom(&mut self.wl_storage, &address, denom).unwrap(); - let mut total_balance_for_token = token::Amount::default(); - for (owner, amount) in balances { - total_balance_for_token += amount; - credit_tokens(&mut self.wl_storage, &address, &owner, amount) - .unwrap(); + self.wl_storage + .write(&protocol_pk_key(address), &protocol_key.pk.raw) + .expect("Unable to set genesis user protocol public key"); + + self.wl_storage + .write(&dkg_session_keys::dkg_pk_key(address), &dkg_key.raw) + .expect( + "Unable to set genesis user public DKG session key", + ); + + // TODO: replace pos::init_genesis validators arg with + // init_genesis_validator from here + if let Err(err) = pos::namada_proof_of_stake::become_validator( + BecomeValidator { + storage: &mut self.wl_storage, + params, + address, + consensus_key: &consensus_key.pk.raw, + protocol_key: &protocol_key.pk.raw, + eth_cold_key: ð_cold_key.pk.raw, + eth_hot_key: ð_hot_key.pk.raw, + current_epoch, + commission_rate: *commission_rate, + max_commission_rate_change: *max_commission_rate_change, + offset_opt: Some(0), + }, + ) { + tracing::warn!( + "Genesis init genesis validator tx for {alias} failed \ + with {err}. Skipping." + ); + continue; + } } - // Write the total amount of tokens for the ratio - self.wl_storage - .write( - &token::minted_balance_key(&address), - total_balance_for_token, - ) - .unwrap(); } } - /// Initialize genesis validator accounts - fn initialize_validators( + /// Apply genesis txs to transfer tokens + fn apply_genesis_txs_transfer( &mut self, - staking_token: &Address, - validators: &[genesis::Validator], - implicit_vp_code_path: &str, + genesis: &genesis::chain::Finalized, ) { - // Initialize genesis validator accounts - for validator in validators { - let vp_code_hash = read_wasm_hash( - &self.wl_storage, - &validator.validator_vp_code_path, - ) - .unwrap() - .ok_or(Error::LoadingWasm(format!( - "Unknown vp code path: {}", - implicit_vp_code_path - ))) - .expect("Reading wasms should not fail"); - - #[cfg(not(feature = "dev"))] + if let Some(txs) = &genesis.transactions.transfer { + for TransferTx { + token, + source, + target, + amount, + .. + } in txs { - assert_eq!( - vp_code_hash.0.as_slice(), - &validator.validator_vp_sha256, - "Invalid validator VP sha256 hash for {}", - validator.validator_vp_code_path + let token = match genesis.get_token_address(token) { + Some(token) => { + tracing::debug!( + "Applying genesis tx to transfer {} of token \ + {token} from {source} to {target}", + amount + ); + token + } + None => { + tracing::warn!( + "Genesis token transfer tx uses an unknown token \ + alias {token}. Skipping." + ); + continue; + } + }; + let target = match genesis.get_user_address(target) { + Some(target) => target, + None => { + tracing::warn!( + "Genesis token transfer tx uses an unknown target \ + alias {target}. Skipping." + ); + continue; + } + }; + let source: Address = (&source.raw).into(); + tracing::debug!( + "Transfer addresses: token {token} from {source} to \ + {target}" ); - } - - let addr = &validator.pos_data.address; - self.wl_storage - .write_bytes(&Key::validity_predicate(addr), vp_code_hash) - .expect("Unable to write user VP"); - // Validator account key - storage_api::account::set_public_key_at( - &mut self.wl_storage, - addr, - &validator.account_key, - 0, - ) - .unwrap(); - // Balances - // Account balance (tokens not staked in PoS) - credit_tokens( - &mut self.wl_storage, - staking_token, - addr, - validator.non_staked_balance, - ) - .unwrap(); - - self.wl_storage - .write( - &dkg_session_keys::dkg_pk_key(addr), - &validator.dkg_public_key, - ) - .expect("Unable to set genesis user public DKG session key"); + if let Err(err) = storage_api::token::transfer( + &mut self.wl_storage, + token, + &source, + &target, + amount.amount, + ) { + tracing::warn!( + "Genesis token transfer tx failed with: {err}. \ + Skipping." + ); + continue; + }; + } } } - /// Initialize the PoS and set the initial validator set - fn set_initial_validators( - &mut self, - staking_token: &Address, - validators: Vec, - pos_params: &OwnedPosParams, - ) -> Result { - let mut response = response::InitChain::default(); - // PoS system depends on epoch being initialized. Write the total - // genesis staking token balance to storage after - // initialization. + /// Apply genesis txs to transfer tokens + fn apply_genesis_txs_bonds(&mut self, genesis: &genesis::chain::Finalized) { let (current_epoch, _gas) = self.wl_storage.storage.get_current_epoch(); - pos::init_genesis_storage( - &mut self.wl_storage, - pos_params, - validators.into_iter().map(|validator| validator.pos_data), - current_epoch, - ); - - let total_nam = read_total_supply(&self.wl_storage, staking_token)?; - // At this stage in the chain genesis, the PoS address balance is the - // same as the number of staked tokens - let total_staked_nam = - read_balance(&self.wl_storage, staking_token, &address::POS)?; - - tracing::info!( - "Genesis total native tokens: {}.", - total_nam.to_string_native() - ); - tracing::info!( - "Total staked tokens: {}.", - total_staked_nam.to_string_native() - ); - - // Set the ratio of staked to total NAM tokens in the parameters storage - parameters::update_staked_ratio_parameter( - &mut self.wl_storage, - &(Dec::from(total_staked_nam) / Dec::from(total_nam)), - ) - .expect("unable to set staked ratio of NAM in storage"); - - ibc::init_genesis_storage(&mut self.wl_storage); - - // Set the initial validator set - response.validators = self - .get_abci_validator_updates(true) - .expect("Must be able to set genesis validator set"); - debug_assert!(!response.validators.is_empty()); - - Ok(response) - } -} + if let Some(txs) = &genesis.transactions.bond { + for BondTx { + source, + validator, + amount, + .. + } in txs + { + tracing::debug!( + "Applying genesis tx to bond {} native tokens from \ + {source} to {validator}", + amount, + ); -fn read_wasm_hash( - storage: &impl StorageRead, - path: impl AsRef, -) -> storage_api::Result> { - let hash_key = Key::wasm_hash(path); - match storage.read_bytes(&hash_key)? { - Some(value) => { - let hash = CodeHash::try_from(&value[..]).into_storage_result()?; - Ok(Some(hash)) + let source = match source { + genesis::transactions::AliasOrPk::Alias(alias) => { + match genesis.get_user_address(alias) { + Some(addr) => addr, + None => { + tracing::warn!( + "Cannot find bond source address with \ + alias \"{alias}\". Skipping." + ); + continue; + } + } + } + genesis::transactions::AliasOrPk::PublicKey(pk) => { + Address::from(&pk.raw) + } + }; + + let validator = match genesis.get_validator_address(validator) { + Some(addr) => addr, + None => { + tracing::warn!( + "Cannot find bond validator address with alias \ + \"{validator}\". Skipping." + ); + continue; + } + }; + + if let Err(err) = pos::namada_proof_of_stake::bond_tokens( + &mut self.wl_storage, + Some(&source), + validator, + amount.amount, + current_epoch, + Some(0), + ) { + tracing::warn!( + "Genesis bond tx failed with: {err}. Skipping." + ); + continue; + }; + } } - None => Ok(None), } } diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index e647d1eebb..4c924400fc 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -85,9 +85,6 @@ use crate::facade::tower_abci::{request, response}; use crate::node::ledger::shims::abcipp_shim_types::shim; use crate::node::ledger::shims::abcipp_shim_types::shim::response::TxResult; use crate::node::ledger::{storage, tendermint_node}; -#[cfg(feature = "dev")] -use crate::wallet; -#[allow(unused_imports)] use crate::wallet::{ValidatorData, ValidatorKeys}; fn key_to_tendermint( @@ -372,8 +369,7 @@ where H: StorageHasher + Sync + 'static, { /// The id of the current chain - #[allow(dead_code)] - chain_id: ChainId, + pub chain_id: ChainId, /// The persistent storage with write log pub wl_storage: WlStorage, /// Byzantine validators given from ABCI++ `prepare_proposal` are stored in @@ -439,7 +435,6 @@ where db_cache: Option<&D::Cache>, vp_wasm_compilation_cache: u64, tx_wasm_compilation_cache: u64, - native_token: Address, ) -> Self { let chain_id = config.chain_id; let db_path = config.shell.db_dir(&chain_id); @@ -451,6 +446,18 @@ where std::fs::create_dir(&base_dir) .expect("Creating directory for Namada should not fail"); } + let native_token = if cfg!(feature = "integration") + || (!cfg!(test) && !cfg!(feature = "benches")) + { + let chain_dir = base_dir.join(chain_id.as_str()); + let genesis = + genesis::chain::Finalized::read_toml_files(&chain_dir) + .expect("Missing genesis files"); + genesis.get_native_token().clone() + } else { + address::nam() + }; + // load last state from storage let mut storage = Storage::open( db_path, @@ -472,26 +479,19 @@ where // load in keys and address from wallet if mode is set to `Validator` let mode = match mode { TendermintMode::Validator => { - #[cfg(not(feature = "dev"))] + #[cfg(not(test))] { let wallet_path = &base_dir.join(chain_id.as_str()); - let genesis_path = - &base_dir.join(format!("{}.toml", chain_id.as_str())); tracing::debug!( - "{}", - wallet_path.as_path().to_str().unwrap() - ); - let mut wallet = crate::wallet::load_or_new_from_genesis( - wallet_path, - genesis::genesis_config::open_genesis_config( - genesis_path, - ) - .unwrap(), + "Loading wallet from {}", + wallet_path.to_string_lossy() ); + let mut wallet = crate::wallet::load(wallet_path) + .expect("Validator node must have a wallet"); wallet .take_validator_data() .map(|data| ShellMode::Validator { - data: data.clone(), + data, broadcast_sender, eth_oracle, }) @@ -500,14 +500,15 @@ where wallet", ) } - #[cfg(feature = "dev")] + #[cfg(test)] { let (protocol_keypair, eth_bridge_keypair, dkg_keypair) = - wallet::defaults::validator_keys(); + crate::wallet::defaults::validator_keys(); ShellMode::Validator { - data: wallet::ValidatorData { - address: wallet::defaults::validator_address(), - keys: wallet::ValidatorKeys { + data: ValidatorData { + address: crate::wallet::defaults::validator_address( + ), + keys: ValidatorKeys { protocol_keypair, eth_bridge_keypair, dkg_keypair: Some(dkg_keypair), @@ -1496,6 +1497,7 @@ mod test_utils { use data_encoding::HEXUPPER; use namada::core::ledger::storage::EPOCH_SWITCH_BLOCKS_DELAY; + use namada::ledger::parameters::{EpochDuration, Parameters}; use namada::ledger::storage::mockdb::MockDB; use namada::ledger::storage::{ update_allowed_conversions, LastBlock, Sha256Hasher, @@ -1512,7 +1514,7 @@ mod test_utils { use namada::types::keccak::KeccakHash; use namada::types::key::*; use namada::types::storage::{BlockHash, Epoch, Header}; - use namada::types::time::DateTimeUtc; + use namada::types::time::{DateTimeUtc, DurationSecs}; use namada::types::transaction::{Fee, TxType, WrapperTx}; use tempfile::tempdir; use tokio::sync::mpsc::{Sender, UnboundedReceiver}; @@ -1690,7 +1692,6 @@ mod test_utils { None, vp_wasm_compilation_cache, tx_wasm_compilation_cache, - address::nam(), ); shell.wl_storage.storage.block.height = height.into(); (Self { shell }, receiver, eth_sender, control_receiver) @@ -1968,7 +1969,6 @@ mod test_utils { /// We test that on shell shutdown, the tx queue gets persisted in a DB, and /// on startup it is read successfully - #[cfg(feature = "testing")] #[test] fn test_tx_queue_persistence() { let base_dir = tempdir().unwrap().as_ref().canonicalize().unwrap(); @@ -1999,14 +1999,12 @@ mod test_utils { None, vp_wasm_compilation_cache, tx_wasm_compilation_cache, - native_token.clone(), ); shell .wl_storage .storage .begin_block(BlockHash::default(), BlockHeight(1)) .expect("begin_block failed"); - token::testing::init_token_storage(&mut shell.wl_storage, 60); let keypair = gen_keypair(); // enqueue a wrapper tx let mut wrapper = @@ -2036,6 +2034,74 @@ mod test_utils { .block .pred_epochs .new_epoch(BlockHeight(1)); + // initialize parameter storage + let params = Parameters { + epoch_duration: EpochDuration { + min_num_of_blocks: 1, + min_duration: DurationSecs(3600), + }, + max_expected_time_per_block: DurationSecs(3600), + max_proposal_bytes: Default::default(), + max_block_gas: 100, + vp_whitelist: vec![], + tx_whitelist: vec![], + implicit_vp_code_hash: Default::default(), + epochs_per_year: 365, + max_signatures_per_transaction: 10, + pos_gain_p: Default::default(), + pos_gain_d: Default::default(), + staked_ratio: Default::default(), + pos_inflation_amount: Default::default(), + fee_unshielding_gas_limit: 0, + fee_unshielding_descriptions_limit: 0, + minimum_gas_price: Default::default(), + }; + params + .init_storage(&mut shell.wl_storage) + .expect("Test failed"); + // make wl_storage to update conversion for a new epoch + let token_params = token::Parameters { + max_reward_rate: Default::default(), + kd_gain_nom: Default::default(), + kp_gain_nom: Default::default(), + locked_ratio_target: Default::default(), + }; + // Insert a map assigning random addresses to each token alias. + // Needed for storage but not for this test. + for (token, _) in address::tokens() { + let addr = address::gen_deterministic_established_address(token); + token_params.init_storage(&addr, &mut shell.wl_storage); + shell + .wl_storage + .write(&token::minted_balance_key(&addr), token::Amount::zero()) + .unwrap(); + shell + .wl_storage + .storage + .conversion_state + .tokens + .insert(token.to_string(), addr); + } + shell.wl_storage.storage.conversion_state.tokens.insert( + "nam".to_string(), + shell.wl_storage.storage.native_token.clone(), + ); + token_params.init_storage( + &shell.wl_storage.storage.native_token.clone(), + &mut shell.wl_storage, + ); + // final adjustments so that updating allowed conversions doesn't panic + // with divide by zero + shell + .wl_storage + .write( + &token::minted_balance_key( + &shell.wl_storage.storage.native_token.clone(), + ), + token::Amount::zero(), + ) + .unwrap(); + shell.wl_storage.storage.conversion_state.normed_inflation = Some(1); update_allowed_conversions(&mut shell.wl_storage) .expect("update conversions failed"); shell.wl_storage.commit_block().expect("commit failed"); @@ -2065,7 +2131,6 @@ mod test_utils { None, vp_wasm_compilation_cache, tx_wasm_compilation_cache, - address::nam(), ); assert!(!shell.wl_storage.storage.tx_queue.is_empty()); } diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/bridge_pool_vext.rs b/apps/src/lib/node/ledger/shell/vote_extensions/bridge_pool_vext.rs index f817b4cd9f..2cefc82bf4 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/bridge_pool_vext.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/bridge_pool_vext.rs @@ -340,6 +340,7 @@ mod test_bp_vote_extensions { current_epoch: 0.into(), commission_rate: Default::default(), max_commission_rate_change: Default::default(), + offset_opt: None, }) .expect("Test failed"); diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index 0ca7ef39fc..3dd5922e92 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -7,7 +7,6 @@ use std::task::{Context, Poll}; use futures::future::FutureExt; use namada::proof_of_stake::find_validator_by_raw_hash; use namada::proto::Tx; -use namada::types::address::Address; #[cfg(not(feature = "abcipp"))] use namada::types::hash::Hash; use namada::types::key::tm_raw_hash_to_string; @@ -63,7 +62,6 @@ impl AbcippShim { db_cache: &rocksdb::Cache, vp_wasm_compilation_cache: u64, tx_wasm_compilation_cache: u64, - native_token: Address, ) -> (Self, AbciService, broadcast::Sender<()>) { // We can use an unbounded channel here, because tower-abci limits the // the number of requests that can come in @@ -81,7 +79,6 @@ impl AbcippShim { Some(db_cache), vp_wasm_compilation_cache, tx_wasm_compilation_cache, - native_token, ), #[cfg(not(feature = "abcipp"))] begin_block_request: None, diff --git a/apps/src/lib/node/ledger/storage/mod.rs b/apps/src/lib/node/ledger/storage/mod.rs index 43617879c8..7a1f44e6f9 100644 --- a/apps/src/lib/node/ledger/storage/mod.rs +++ b/apps/src/lib/node/ledger/storage/mod.rs @@ -54,6 +54,7 @@ mod tests { use std::collections::HashMap; use itertools::Itertools; + use namada::ledger::parameters::{EpochDuration, Parameters}; use namada::ledger::storage::write_log::WriteLog; use namada::ledger::storage::{ types, update_allowed_conversions, WlStorage, @@ -62,7 +63,8 @@ mod tests { use namada::types::chain::ChainId; use namada::types::hash::Hash; use namada::types::storage::{BlockHash, BlockHeight, Key}; - use namada::types::{address, storage}; + use namada::types::time::DurationSecs; + use namada::types::{address, storage, token}; use proptest::collection::vec; use proptest::prelude::*; use proptest::test_runner::Config; @@ -118,7 +120,6 @@ mod tests { assert_eq!(result, None); } - #[cfg(feature = "testing")] #[test] fn test_commit_block() { let db_path = @@ -136,16 +137,82 @@ mod tests { let key = Key::parse("key").expect("cannot parse the key string"); let value: u64 = 1; let value_bytes = types::encode(&value); - + let mut wl_storage = WlStorage::new(WriteLog::default(), storage); + // initialize parameter storage + let params = Parameters { + epoch_duration: EpochDuration { + min_num_of_blocks: 1, + min_duration: DurationSecs(3600), + }, + max_expected_time_per_block: DurationSecs(3600), + max_proposal_bytes: Default::default(), + max_block_gas: 100, + vp_whitelist: vec![], + tx_whitelist: vec![], + implicit_vp_code_hash: Default::default(), + epochs_per_year: 365, + max_signatures_per_transaction: 10, + pos_gain_p: Default::default(), + pos_gain_d: Default::default(), + staked_ratio: Default::default(), + pos_inflation_amount: Default::default(), + fee_unshielding_gas_limit: 0, + fee_unshielding_descriptions_limit: 0, + minimum_gas_price: Default::default(), + }; + params.init_storage(&mut wl_storage).expect("Test failed"); // insert and commit - storage + wl_storage + .storage .write(&key, value_bytes.clone()) .expect("write failed"); - storage.block.epoch = storage.block.epoch.next(); - storage.block.pred_epochs.new_epoch(BlockHeight(100)); + wl_storage.storage.block.epoch = wl_storage.storage.block.epoch.next(); + wl_storage + .storage + .block + .pred_epochs + .new_epoch(BlockHeight(100)); // make wl_storage to update conversion for a new epoch - let mut wl_storage = WlStorage::new(WriteLog::default(), storage); - namada::types::token::testing::init_token_storage(&mut wl_storage, 60); + + let token_params = token::Parameters { + max_reward_rate: Default::default(), + kd_gain_nom: Default::default(), + kp_gain_nom: Default::default(), + locked_ratio_target: Default::default(), + }; + // Insert a map assigning random addresses to each token alias. + // Needed for storage but not for this test. + for (token, _) in address::tokens() { + let addr = address::gen_deterministic_established_address(token); + token_params.init_storage(&addr, &mut wl_storage); + wl_storage + .write(&token::minted_balance_key(&addr), token::Amount::zero()) + .unwrap(); + wl_storage + .storage + .conversion_state + .tokens + .insert(token.to_string(), addr); + } + wl_storage + .storage + .conversion_state + .tokens + .insert("nam".to_string(), wl_storage.storage.native_token.clone()); + token_params.init_storage( + &wl_storage.storage.native_token.clone(), + &mut wl_storage, + ); + + wl_storage + .write( + &token::minted_balance_key( + &wl_storage.storage.native_token.clone(), + ), + token::Amount::zero(), + ) + .unwrap(); + wl_storage.storage.conversion_state.normed_inflation = Some(1); update_allowed_conversions(&mut wl_storage) .expect("update conversions failed"); wl_storage.commit_block().expect("commit failed"); diff --git a/apps/src/lib/node/ledger/tendermint_node.rs b/apps/src/lib/node/ledger/tendermint_node.rs index 0833f7c3a7..25a2b9e114 100644 --- a/apps/src/lib/node/ledger/tendermint_node.rs +++ b/apps/src/lib/node/ledger/tendermint_node.rs @@ -9,6 +9,7 @@ use namada::types::key::*; use namada::types::storage::BlockHeight; use namada::types::time::DateTimeUtc; use serde_json::json; +use sha2::{Digest, Sha256}; #[cfg(feature = "abcipp")] use tendermint_abcipp::Moniker; use thiserror::Error; @@ -18,12 +19,14 @@ use tokio::process::Command; use crate::cli::namada_version; use crate::config; +use crate::facade::tendermint::node::Id as TendermintNodeId; #[cfg(feature = "abciplus")] use crate::facade::tendermint::Moniker; use crate::facade::tendermint::{block, Genesis}; use crate::facade::tendermint_config::{ Error as TendermintError, TendermintConfig, }; + /// Env. var to output Tendermint log to stdout pub const ENV_VAR_TM_STDOUT: &str = "NAMADA_CMT_STDOUT"; @@ -83,13 +86,6 @@ pub async fn run( let tendermint_path = from_env_or_default()?; let mode = config.shell.tendermint_mode.to_str().to_owned(); - #[cfg(feature = "dev")] - // This has to be checked before we run tendermint init - let has_validator_key = { - let path = home_dir.join("config").join("priv_validator_key.json"); - Path::new(&path).exists() - }; - // init and run a tendermint node child process let output = Command::new(&tendermint_path) .args(["init", &mode, "--home", &home_dir_string]) @@ -100,14 +96,6 @@ pub async fn run( panic!("Tendermint failed to initialize with {:#?}", output); } - #[cfg(feature = "dev")] - { - let consensus_key = crate::wallet::defaults::validator_keypair(); - // write the validator key file if it didn't already exist - if !has_validator_key { - write_validator_key_async(&home_dir, &consensus_key).await; - } - } #[cfg(feature = "abcipp")] write_tm_genesis(&home_dir, chain_id, genesis_time, &config).await; #[cfg(not(feature = "abcipp"))] @@ -342,6 +330,28 @@ pub fn write_validator_state(home_dir: impl AsRef) { .expect("Couldn't write private validator state file"); } +/// Length of a Tendermint Node ID in bytes +const TENDERMINT_NODE_ID_LENGTH: usize = 20; + +/// Derive Tendermint node ID from public key +pub fn id_from_pk(pk: &common::PublicKey) -> TendermintNodeId { + let mut bytes = [0u8; TENDERMINT_NODE_ID_LENGTH]; + + match pk { + common::PublicKey::Ed25519(_) => { + let _pk: ed25519::PublicKey = pk.try_to_pk().unwrap(); + let digest = Sha256::digest(_pk.serialize_to_vec().as_slice()); + bytes.copy_from_slice(&digest[..TENDERMINT_NODE_ID_LENGTH]); + } + common::PublicKey::Secp256k1(_) => { + let _pk: secp256k1::PublicKey = pk.try_to_pk().unwrap(); + let digest = Sha256::digest(_pk.serialize_to_vec().as_slice()); + bytes.copy_from_slice(&digest[..TENDERMINT_NODE_ID_LENGTH]); + } + } + TendermintNodeId::new(bytes) +} + async fn update_tendermint_config( home_dir: impl AsRef, config: TendermintConfig, @@ -354,9 +364,7 @@ async fn update_tendermint_config( Moniker::from_str(&format!("{}-{}", config.moniker, namada_version())) .expect("Invalid moniker"); - // In "dev", only produce blocks when there are txs or when the AppHash - // changes - config.consensus.create_empty_blocks = true; // !cfg!(feature = "dev"); + config.consensus.create_empty_blocks = true; // We set this to true as we don't want any invalid tx be re-applied. This // also implies that it's not possible for an invalid tx to become valid diff --git a/apps/src/lib/wallet/alias.rs b/apps/src/lib/wallet/alias.rs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/apps/src/lib/wallet/cli_utils.rs b/apps/src/lib/wallet/cli_utils.rs deleted file mode 100644 index 9908af12bb..0000000000 --- a/apps/src/lib/wallet/cli_utils.rs +++ /dev/null @@ -1,517 +0,0 @@ -use std::fs::File; -use std::io::{self, Write}; - -use borsh_ext::BorshSerializeExt; -use itertools::sorted; -use masp_primitives::zip32::ExtendedFullViewingKey; -use namada::types::key::{PublicKeyHash, RefTo}; -use namada::types::masp::{MaspValue, PaymentAddress}; -use namada_sdk::masp::find_valid_diversifier; -use namada_sdk::wallet::{DecryptionError, FindKeyError, GenRestoreKeyError}; -use rand_core::OsRng; - -use crate::cli; -use crate::cli::{args, Context}; -use crate::wallet::{read_and_confirm_encryption_password, CliWalletUtils}; - -/// Find shielded address or key -pub fn address_key_find( - ctx: Context, - args::AddrKeyFind { - alias, - unsafe_show_secret, - }: args::AddrKeyFind, -) { - let mut wallet = ctx.wallet; - let alias = alias.to_lowercase(); - if let Ok(viewing_key) = wallet.find_viewing_key(&alias) { - // Check if alias is a viewing key - println!("Viewing key: {}", viewing_key); - if unsafe_show_secret { - // Check if alias is also a spending key - match wallet.find_spending_key(&alias, None) { - Ok(spending_key) => println!("Spending key: {}", spending_key), - Err(FindKeyError::KeyNotFound) => {} - Err(err) => eprintln!("{}", err), - } - } - } else if let Some(payment_addr) = wallet.find_payment_addr(&alias) { - // Failing that, check if alias is a payment address - println!("Payment address: {}", payment_addr); - } else { - // Otherwise alias cannot be referring to any shielded value - println!( - "No shielded address or key with alias {} found. Use the commands \ - `masp list-addrs` and `masp list-keys` to see all the known \ - addresses and keys.", - alias.to_lowercase() - ); - } -} - -/// List spending keys. -pub fn spending_keys_list( - ctx: Context, - args::MaspKeysList { - decrypt, - unsafe_show_secret, - }: args::MaspKeysList, -) { - let wallet = ctx.wallet; - let known_view_keys = wallet.get_viewing_keys(); - let known_spend_keys = wallet.get_spending_keys(); - if known_view_keys.is_empty() { - println!( - "No known keys. Try `masp add --alias my-addr --value ...` to add \ - a new key to the wallet." - ); - } else { - let stdout = std::io::stdout(); - let mut w = stdout.lock(); - writeln!(w, "Known keys:").unwrap(); - for (alias, key) in known_view_keys { - write!(w, " Alias \"{}\"", alias).unwrap(); - let spending_key_opt = known_spend_keys.get(&alias); - // If this alias is associated with a spending key, indicate whether - // or not the spending key is encrypted - // TODO: consider turning if let into match - if let Some(spending_key) = spending_key_opt { - if spending_key.is_encrypted() { - writeln!(w, " (encrypted):") - } else { - writeln!(w, " (not encrypted):") - } - .unwrap(); - } else { - writeln!(w, ":").unwrap(); - } - // Always print the corresponding viewing key - writeln!(w, " Viewing Key: {}", key).unwrap(); - // A subset of viewing keys will have corresponding spending keys. - // Print those too if they are available and requested. - if unsafe_show_secret { - if let Some(spending_key) = spending_key_opt { - match spending_key.get::(decrypt, None) { - // Here the spending key is unencrypted or successfully - // decrypted - Ok(spending_key) => { - writeln!(w, " Spending key: {}", spending_key) - .unwrap(); - } - // Here the key is encrypted but decryption has not been - // requested - Err(DecryptionError::NotDecrypting) if !decrypt => { - continue; - } - // Here the key is encrypted but incorrect password has - // been provided - Err(err) => { - writeln!( - w, - " Couldn't decrypt the spending key: {}", - err - ) - .unwrap(); - } - } - } - } - } - } -} - -/// List payment addresses. -pub fn payment_addresses_list(ctx: Context) { - let wallet = ctx.wallet; - let known_addresses = wallet.get_payment_addrs(); - if known_addresses.is_empty() { - println!( - "No known payment addresses. Try `masp gen-addr --alias my-addr` \ - to generate a new payment address." - ); - } else { - let stdout = std::io::stdout(); - let mut w = stdout.lock(); - writeln!(w, "Known payment addresses:").unwrap(); - for (alias, address) in sorted(known_addresses) { - writeln!(w, " \"{}\": {}", alias, address).unwrap(); - } - } -} - -/// Generate a spending key. -pub fn spending_key_gen( - ctx: Context, - args::MaspSpendKeyGen { - alias, - alias_force, - unsafe_dont_encrypt, - }: args::MaspSpendKeyGen, -) { - let mut wallet = ctx.wallet; - let alias = alias.to_lowercase(); - let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); - let (alias, _key) = wallet.gen_spending_key(alias, password, alias_force); - crate::wallet::save(&wallet).unwrap_or_else(|err| eprintln!("{}", err)); - println!( - "Successfully added a spending key with alias: \"{}\"", - alias - ); -} - -/// Generate a shielded payment address from the given key. -pub fn payment_address_gen( - ctx: Context, - args::MaspPayAddrGen { - alias, - alias_force, - viewing_key, - pin, - }: args::MaspPayAddrGen, -) { - let alias = alias.to_lowercase(); - let viewing_key = ExtendedFullViewingKey::from(viewing_key).fvk.vk; - let (div, _g_d) = find_valid_diversifier(&mut OsRng); - let payment_addr = viewing_key - .to_payment_address(div) - .expect("a PaymentAddress"); - let mut wallet = ctx.wallet; - let alias = wallet - .insert_payment_addr( - alias, - PaymentAddress::from(payment_addr).pinned(pin), - alias_force, - ) - .unwrap_or_else(|| { - eprintln!("Payment address not added"); - cli::safe_exit(1); - }); - crate::wallet::save(&wallet).unwrap_or_else(|err| eprintln!("{}", err)); - println!( - "Successfully generated a payment address with the following alias: {}", - alias, - ); -} - -/// Add a viewing key, spending key, or payment address to wallet. -pub fn address_key_add( - mut ctx: Context, - args::MaspAddrKeyAdd { - alias, - alias_force, - value, - unsafe_dont_encrypt, - }: args::MaspAddrKeyAdd, -) { - let alias = alias.to_lowercase(); - let (alias, typ) = match value { - MaspValue::FullViewingKey(viewing_key) => { - let alias = ctx - .wallet - .insert_viewing_key(alias, viewing_key, alias_force) - .unwrap_or_else(|| { - eprintln!("Viewing key not added"); - cli::safe_exit(1); - }); - (alias, "viewing key") - } - MaspValue::ExtendedSpendingKey(spending_key) => { - let password = - read_and_confirm_encryption_password(unsafe_dont_encrypt); - let alias = ctx - .wallet - .encrypt_insert_spending_key( - alias, - spending_key, - password, - alias_force, - ) - .unwrap_or_else(|| { - eprintln!("Spending key not added"); - cli::safe_exit(1); - }); - (alias, "spending key") - } - MaspValue::PaymentAddress(payment_addr) => { - let alias = ctx - .wallet - .insert_payment_addr(alias, payment_addr, alias_force) - .unwrap_or_else(|| { - eprintln!("Payment address not added"); - cli::safe_exit(1); - }); - (alias, "payment address") - } - }; - crate::wallet::save(&ctx.wallet).unwrap_or_else(|err| eprintln!("{}", err)); - println!( - "Successfully added a {} with the following alias to wallet: {}", - typ, alias, - ); -} - -/// Restore a keypair and an implicit address from the mnemonic code in the -/// wallet. -pub fn key_and_address_restore( - ctx: Context, - args::KeyAndAddressRestore { - scheme, - alias, - alias_force, - unsafe_dont_encrypt, - derivation_path, - }: args::KeyAndAddressRestore, -) { - let mut wallet = ctx.wallet; - let encryption_password = - read_and_confirm_encryption_password(unsafe_dont_encrypt); - let (alias, _key) = wallet - .derive_key_from_user_mnemonic_code( - scheme, - alias, - alias_force, - derivation_path, - None, - encryption_password, - ) - .unwrap_or_else(|err| { - eprintln!("{}", err); - cli::safe_exit(1) - }) - .unwrap_or_else(|| { - println!("No changes are persisted. Exiting."); - cli::safe_exit(0); - }); - crate::wallet::save(&wallet).unwrap_or_else(|err| eprintln!("{}", err)); - println!( - "Successfully added a key and an address with alias: \"{}\"", - alias - ); -} - -/// Generate a new keypair and derive implicit address from it and store them in -/// the wallet. -pub fn key_and_address_gen( - ctx: Context, - args::KeyAndAddressGen { - scheme, - alias, - alias_force, - unsafe_dont_encrypt, - derivation_path, - }: args::KeyAndAddressGen, -) { - let mut wallet = ctx.wallet; - let encryption_password = - read_and_confirm_encryption_password(unsafe_dont_encrypt); - let mut rng = OsRng; - let derivation_path_and_mnemonic_rng = - derivation_path.map(|p| (p, &mut rng)); - let (alias, _key, _mnemonic) = wallet - .gen_key( - scheme, - alias, - alias_force, - None, - encryption_password, - derivation_path_and_mnemonic_rng, - ) - .unwrap_or_else(|err| match err { - GenRestoreKeyError::KeyStorageError => { - println!("No changes are persisted. Exiting."); - cli::safe_exit(0); - } - _ => { - eprintln!("{}", err); - cli::safe_exit(1); - } - }); - crate::wallet::save(&wallet).unwrap_or_else(|err| eprintln!("{}", err)); - println!( - "Successfully added a key and an address with alias: \"{}\"", - alias - ); -} - -/// Find a keypair in the wallet store. -pub fn key_find( - ctx: Context, - args::KeyFind { - public_key, - alias, - value, - unsafe_show_secret, - }: args::KeyFind, -) { - let mut wallet = ctx.wallet; - let found_keypair = match public_key { - Some(pk) => wallet.find_key_by_pk(&pk, None), - None => { - let alias = alias.or(value); - match alias { - None => { - eprintln!( - "An alias, public key or public key hash needs to be \ - supplied" - ); - cli::safe_exit(1) - } - Some(alias) => wallet.find_key(alias.to_lowercase(), None), - } - } - }; - match found_keypair { - Ok(keypair) => { - let pkh: PublicKeyHash = (&keypair.ref_to()).into(); - println!("Public key hash: {}", pkh); - println!("Public key: {}", keypair.ref_to()); - if unsafe_show_secret { - println!("Secret key: {}", keypair); - } - } - Err(err) => { - eprintln!("{}", err); - } - } -} - -/// List all known keys. -pub fn key_list( - ctx: Context, - args::KeyList { - decrypt, - unsafe_show_secret, - }: args::KeyList, -) { - let wallet = ctx.wallet; - let known_keys = wallet.get_keys(); - if known_keys.is_empty() { - println!( - "No known keys. Try `key gen --alias my-key` to generate a new \ - key." - ); - } else { - let stdout = io::stdout(); - let mut w = stdout.lock(); - writeln!(w, "Known keys:").unwrap(); - for (alias, (stored_keypair, pkh)) in known_keys { - let encrypted = if stored_keypair.is_encrypted() { - "encrypted" - } else { - "not encrypted" - }; - writeln!(w, " Alias \"{}\" ({}):", alias, encrypted).unwrap(); - if let Some(pkh) = pkh { - writeln!(w, " Public key hash: {}", pkh).unwrap(); - } - match stored_keypair.get::(decrypt, None) { - Ok(keypair) => { - writeln!(w, " Public key: {}", keypair.ref_to()) - .unwrap(); - if unsafe_show_secret { - writeln!(w, " Secret key: {}", keypair).unwrap(); - } - } - Err(DecryptionError::NotDecrypting) if !decrypt => { - continue; - } - Err(err) => { - writeln!(w, " Couldn't decrypt the keypair: {}", err) - .unwrap(); - } - } - } - } -} - -/// Export a keypair to a file. -pub fn key_export(ctx: Context, args::KeyExport { alias }: args::KeyExport) { - let mut wallet = ctx.wallet; - wallet - .find_key(alias.to_lowercase(), None) - .map(|keypair| { - let file_data = keypair.serialize_to_vec(); - let file_name = format!("key_{}", alias.to_lowercase()); - let mut file = File::create(&file_name).unwrap(); - - file.write_all(file_data.as_ref()).unwrap(); - println!("Exported to file {}", file_name); - }) - .unwrap_or_else(|err| { - eprintln!("{}", err); - cli::safe_exit(1) - }) -} - -/// List all known addresses. -pub fn address_list(ctx: Context) { - let wallet = ctx.wallet; - let known_addresses = wallet.get_addresses(); - if known_addresses.is_empty() { - println!( - "No known addresses. Try `address gen --alias my-addr` to \ - generate a new implicit address." - ); - } else { - let stdout = io::stdout(); - let mut w = stdout.lock(); - writeln!(w, "Known addresses:").unwrap(); - for (alias, address) in sorted(known_addresses) { - writeln!(w, " \"{}\": {}", alias, address.to_pretty_string()) - .unwrap(); - } - } -} - -/// Find address (alias) by its alias (address). -pub fn address_or_alias_find(ctx: Context, args: args::AddressOrAliasFind) { - let wallet = ctx.wallet; - if args.address.is_some() && args.alias.is_some() { - panic!( - "This should not be happening: clap should emit its own error \ - message." - ); - } else if args.alias.is_some() { - if let Some(address) = wallet.find_address(args.alias.as_ref().unwrap()) - { - println!("Found address {}", address.to_pretty_string()); - } else { - println!( - "No address with alias {} found. Use the command `address \ - list` to see all the known addresses.", - args.alias.unwrap().to_lowercase() - ); - } - } else if args.address.is_some() { - if let Some(alias) = wallet.find_alias(args.address.as_ref().unwrap()) { - println!("Found alias {}", alias); - } else { - println!( - "No alias with address {} found. Use the command `address \ - list` to see all the known addresses.", - args.address.unwrap() - ); - } - } -} - -/// Add an address to the wallet. -pub fn address_add(ctx: Context, args: args::AddressAdd) { - let mut wallet = ctx.wallet; - if wallet - .add_address( - args.alias.clone().to_lowercase(), - args.address, - args.alias_force, - ) - .is_none() - { - eprintln!("Address not added"); - cli::safe_exit(1); - } - crate::wallet::save(&wallet).unwrap_or_else(|err| eprintln!("{}", err)); - println!( - "Successfully added a key and an address with alias: \"{}\"", - args.alias.to_lowercase() - ); -} diff --git a/apps/src/lib/wallet/defaults.rs b/apps/src/lib/wallet/defaults.rs index 82a9524daa..57894d002e 100644 --- a/apps/src/lib/wallet/defaults.rs +++ b/apps/src/lib/wallet/defaults.rs @@ -1,78 +1,14 @@ //! Default addresses and keys. -#[cfg(any(test, feature = "dev"))] +#[cfg(any(test, feature = "testing", feature = "benches"))] pub use dev::{ addresses, albert_address, albert_keypair, bertha_address, bertha_keypair, christel_address, christel_keypair, daewon_address, daewon_keypair, - ester_address, ester_keypair, keys, validator_address, validator_keypair, - validator_keys, + ester_address, ester_keypair, keys, tokens, validator_address, + validator_keypair, validator_keys, }; -use namada::core::ledger::eth_bridge::storage::bridge_pool::BRIDGE_POOL_ADDRESS; -use namada::ledger::{governance, pgf, pos}; -use namada::types::address::Address; -use namada::types::key::*; -use namada_sdk::wallet::alias::Alias; -use crate::config::genesis::genesis_config::GenesisConfig; - -/// The default addresses with their aliases. -pub fn addresses_from_genesis(genesis: GenesisConfig) -> Vec<(Alias, Address)> { - // Internal addresses - let mut addresses: Vec<(Alias, Address)> = vec![ - ("pos".into(), pos::ADDRESS), - ("pos_slash_pool".into(), pos::SLASH_POOL_ADDRESS), - ("governance".into(), governance::ADDRESS), - ("eth_bridge".into(), namada_sdk::eth_bridge::ADDRESS), - ("bridge_pool".into(), BRIDGE_POOL_ADDRESS), - ("pgf".into(), pgf::ADDRESS), - ]; - // Genesis validators - let validator_addresses = - genesis.validator.into_iter().map(|(alias, validator)| { - // The address must be set in the genesis config file - ( - alias.into(), - Address::decode(validator.address.unwrap()).unwrap(), - ) - }); - addresses.extend(validator_addresses); - // Genesis tokens - let token_addresses = genesis.token.into_iter().map(|(alias, token)| { - // The address must be set in the genesis config file - ( - alias.into(), - Address::decode(token.address.unwrap()).unwrap(), - ) - }); - addresses.extend(token_addresses); - // Genesis established accounts - if let Some(accounts) = genesis.established { - let est_addresses = accounts.into_iter().map(|(alias, established)| { - // The address must be set in the genesis config file - ( - alias.into(), - Address::decode(established.address.unwrap()).unwrap(), - ) - }); - addresses.extend(est_addresses); - } - // Genesis implicit accounts - if let Some(accounts) = genesis.implicit { - let imp_addresses = - accounts.into_iter().filter_map(|(alias, implicit)| { - // The public key may not be revealed, only add it if it is - implicit.public_key.map(|pk| { - let pk: common::PublicKey = pk.to_public_key().unwrap(); - let addr: Address = (&pk).into(); - (alias.into(), addr) - }) - }); - addresses.extend(imp_addresses); - } - addresses -} - -#[cfg(any(test, feature = "dev"))] +#[cfg(any(test, feature = "testing", feature = "benches"))] mod dev { use std::collections::HashMap; @@ -85,27 +21,30 @@ mod dev { use namada::types::key::*; use namada_sdk::wallet::alias::Alias; - /// Generate a new protocol signing keypair, eth hot key and DKG session - /// keypair + /// N.B. these are the corresponding values from + /// `genesis/pre-genesis/validator-0/validator-wallet.toml`. + /// + /// If that wallet is regenerated, these values must be changed to fix unit + /// tests. pub fn validator_keys() -> (common::SecretKey, common::SecretKey, DkgKeypair) { // ed25519 bytes let bytes: [u8; 33] = [ - 0, 200, 107, 23, 252, 78, 80, 8, 164, 142, 3, 194, 33, 12, 250, - 169, 211, 127, 47, 13, 194, 54, 199, 81, 102, 246, 189, 119, 144, - 25, 27, 113, 222, + 0, 217, 87, 83, 250, 179, 159, 135, 229, 194, 14, 202, 177, 38, + 144, 254, 250, 103, 233, 113, 100, 202, 111, 23, 214, 122, 235, + 165, 8, 131, 185, 61, 222, ]; // secp256k1 bytes let eth_bridge_key_bytes = [ - 1, 117, 93, 118, 129, 202, 67, 51, 62, 202, 196, 130, 244, 5, 44, - 88, 200, 121, 169, 11, 227, 79, 223, 74, 88, 49, 132, 213, 59, 64, - 20, 13, 82, + 1, 38, 59, 91, 81, 119, 89, 252, 48, 195, 171, 237, 19, 144, 123, + 117, 231, 121, 218, 231, 14, 54, 117, 19, 90, 120, 141, 231, 199, + 7, 110, 254, 191, ]; // DkgKeypair let dkg_bytes = [ - 32, 0, 0, 0, 210, 193, 55, 24, 92, 233, 23, 2, 73, 204, 221, 107, - 110, 222, 192, 136, 54, 24, 108, 236, 137, 27, 121, 142, 142, 7, - 193, 248, 155, 56, 51, 21, + 32, 0, 0, 0, 208, 36, 153, 32, 179, 193, 163, 222, 29, 238, 154, + 53, 181, 71, 213, 162, 59, 130, 225, 93, 57, 20, 161, 254, 52, 1, + 172, 184, 112, 189, 160, 102, ]; ( @@ -128,8 +67,8 @@ mod dev { ] } - /// Deprecated function, soon to be deleted. Generates default tokens - fn tokens() -> HashMap { + /// The default tokens with their aliases. + pub fn tokens() -> HashMap { vec![ (nam(), "NAM"), (btc(), "BTC"), @@ -195,57 +134,57 @@ mod dev { Address::decode("atest1v4ehgw36ggcnsdee8qerswph8y6ry3p5xgunvve3xaqngd3kxc6nqwz9gseyydzzg5unys3ht2n48q").expect("The token address decoding shouldn't fail") } + /// N.B. this is the corresponding value from + /// `genesis/pre-genesis/wallet.toml`. pub fn albert_keypair() -> common::SecretKey { - // generated from - // [`namada::types::key::ed25519::gen_keypair`] let bytes = [ - 115, 191, 32, 247, 18, 101, 5, 106, 26, 203, 48, 145, 39, 41, 41, - 196, 252, 190, 245, 222, 96, 209, 34, 36, 40, 214, 169, 156, 235, - 78, 188, 33, + 131, 49, 140, 204, 234, 198, 192, 138, 1, 119, 102, 120, 64, 180, + 185, 63, 14, 69, 94, 69, 212, 195, 140, 40, 183, 59, 143, 132, 98, + 251, 245, 72, ]; let ed_sk = ed25519::SecretKey::try_from_slice(&bytes).unwrap(); ed_sk.try_to_sk().unwrap() } + /// N.B. this is the corresponding value from + /// `genesis/pre-genesis/wallet.toml`. pub fn bertha_keypair() -> common::SecretKey { - // generated from - // [`namada::types::key::ed25519::gen_keypair`] let bytes = [ - 240, 3, 224, 69, 201, 148, 60, 53, 112, 79, 80, 107, 101, 127, 186, - 6, 176, 162, 113, 224, 62, 8, 183, 187, 124, 234, 244, 251, 92, 36, - 119, 243, + 115, 237, 97, 129, 119, 32, 210, 119, 132, 231, 169, 188, 164, 166, + 6, 104, 215, 99, 166, 247, 236, 172, 45, 69, 237, 31, 36, 26, 165, + 197, 158, 153, ]; let ed_sk = ed25519::SecretKey::try_from_slice(&bytes).unwrap(); ed_sk.try_to_sk().unwrap() } + /// N.B. this is the corresponding value from + /// `genesis/pre-genesis/wallet.toml`. pub fn christel_keypair() -> common::SecretKey { - // generated from - // [`namada::types::key::ed25519::gen_keypair`] let bytes = [ - 65, 198, 96, 145, 237, 227, 84, 182, 107, 55, 209, 235, 115, 105, - 71, 190, 234, 137, 176, 188, 181, 174, 183, 49, 131, 230, 46, 39, - 70, 20, 130, 253, + 54, 37, 185, 57, 165, 142, 246, 4, 2, 215, 207, 143, 192, 66, 80, + 2, 108, 193, 186, 144, 204, 48, 40, 175, 28, 230, 178, 43, 232, 87, + 255, 199, ]; let ed_sk = ed25519::SecretKey::try_from_slice(&bytes).unwrap(); ed_sk.try_to_sk().unwrap() } + /// N.B. this is the corresponding value from + /// `genesis/pre-genesis/wallet.toml`. pub fn daewon_keypair() -> common::SecretKey { - // generated from - // [`namada::types::key::ed25519::gen_keypair`] let bytes = [ - 235, 250, 15, 1, 145, 250, 172, 218, 247, 27, 63, 212, 60, 47, 164, - 57, 187, 156, 182, 144, 107, 174, 38, 81, 37, 40, 19, 142, 68, 135, - 57, 50, + 209, 158, 34, 108, 14, 125, 18, 61, 121, 245, 144, 139, 89, 72, + 212, 196, 97, 182, 106, 95, 138, 169, 86, 0, 194, 139, 85, 171, + 111, 93, 199, 114, ]; let ed_sk = ed25519::SecretKey::try_from_slice(&bytes).unwrap(); ed_sk.try_to_sk().unwrap() } + /// N.B. this is the corresponding value from + /// `genesis/pre-genesis/wallet.toml`. pub fn ester_keypair() -> common::SecretKey { - // generated from - // [`namada::types::key::secp256k1::gen_keypair`] let bytes = [ 54, 144, 147, 226, 3, 93, 132, 247, 42, 126, 90, 23, 200, 155, 122, 147, 139, 93, 8, 204, 135, 178, 40, 152, 5, 227, 175, 204, 102, @@ -255,13 +194,15 @@ mod dev { sk.try_to_sk().unwrap() } + /// N.B. this is the consensus key from + /// `genesis/pre-genesis/validator-0/validator-wallet.toml`. + /// If that wallet is regenerated, this value must be changed to fix unit + /// tests. pub fn validator_keypair() -> common::SecretKey { - // generated from - // [`namada::types::key::ed25519::gen_keypair`] let bytes = [ - 80, 110, 166, 33, 135, 254, 34, 138, 253, 44, 214, 71, 50, 230, 39, - 246, 124, 201, 68, 138, 194, 251, 192, 36, 55, 160, 211, 68, 65, - 189, 121, 217, + 194, 41, 223, 103, 103, 178, 152, 145, 161, 212, 82, 133, 69, 13, + 133, 136, 238, 11, 198, 182, 29, 41, 75, 249, 88, 0, 28, 215, 217, + 63, 234, 78, ]; let ed_sk = ed25519::SecretKey::try_from_slice(&bytes).unwrap(); ed_sk.try_to_sk().unwrap() diff --git a/apps/src/lib/wallet/mod.rs b/apps/src/lib/wallet/mod.rs index 18818daef5..140a85f69d 100644 --- a/apps/src/lib/wallet/mod.rs +++ b/apps/src/lib/wallet/mod.rs @@ -1,22 +1,18 @@ -pub mod cli_utils; pub mod defaults; pub mod pre_genesis; mod store; use std::io::{self, Write}; use std::path::{Path, PathBuf}; -use std::str::FromStr; use std::{env, fs}; use namada::bip39::{Language, Mnemonic}; -use namada::types::address::Address; use namada::types::key::*; pub use namada_sdk::wallet::alias::Alias; use namada_sdk::wallet::fs::FsWalletStorage; use namada_sdk::wallet::store::Store; use namada_sdk::wallet::{ - AddressVpType, ConfirmationResponse, FindKeyError, GenRestoreKeyError, - Wallet, WalletIo, + ConfirmationResponse, FindKeyError, GenRestoreKeyError, Wallet, WalletIo, }; pub use namada_sdk::wallet::{ValidatorData, ValidatorKeys}; use rand_core::OsRng; @@ -24,8 +20,6 @@ pub use store::wallet_file; use zeroize::Zeroizing; use crate::cli; -use crate::config::genesis::genesis_config::GenesisConfig; - #[derive(Debug, Clone)] pub struct CliWalletUtils { store_dir: PathBuf, @@ -239,23 +233,6 @@ where .transpose() } -/// Add addresses from a genesis configuration. -pub fn add_genesis_addresses( - wallet: &mut Wallet, - genesis: GenesisConfig, -) { - for (alias, config) in &genesis.token { - let exp_addr = format!("Genesis token {alias} must have address"); - let address = - Address::from_str(config.address.as_ref().expect(&exp_addr)) - .expect("Valid address"); - wallet.add_vp_type_to_address(AddressVpType::Token, address); - } - for (alias, addr) in defaults::addresses_from_genesis(genesis) { - wallet.add_address(alias.normalize(), addr, true); - } -} - /// Save the wallet store to a file. pub fn save(wallet: &Wallet) -> std::io::Result<()> { wallet @@ -266,10 +243,9 @@ pub fn save(wallet: &Wallet) -> std::io::Result<()> { /// Load a wallet from the store file. pub fn load(store_dir: &Path) -> Option> { let mut wallet = CliWalletUtils::new(store_dir.to_path_buf()); - wallet.load().unwrap_or_else(|err| { - eprintln!("Unable to load the wallet: {}", err); - cli::safe_exit(1) - }); + if wallet.load().is_err() { + return None; + } Some(wallet) } @@ -285,20 +261,10 @@ pub fn load_or_new(store_dir: &Path) -> Wallet { wallet } -/// Load a wallet from the store file or create a new one with the default -/// addresses loaded from the genesis file, if not found. -pub fn load_or_new_from_genesis( - store_dir: &Path, - genesis_cfg: GenesisConfig, -) -> Wallet { - let store = self::store::load_or_new_from_genesis(store_dir, genesis_cfg) - .unwrap_or_else(|err| { - eprintln!("Unable to load the wallet: {}", err); - cli::safe_exit(1) - }); - let mut wallet = CliWalletUtils::new(store_dir.to_path_buf()); - *wallet.store_mut() = store; - wallet +/// Check if a wallet exists in the given store dir. +pub fn exists(store_dir: &Path) -> bool { + let file = wallet_file(store_dir); + file.exists() } /// Read the password for encryption from the file/env/stdin, with diff --git a/apps/src/lib/wallet/pre_genesis.rs b/apps/src/lib/wallet/pre_genesis.rs index da12c2dcce..6144c857af 100644 --- a/apps/src/lib/wallet/pre_genesis.rs +++ b/apps/src/lib/wallet/pre_genesis.rs @@ -14,7 +14,7 @@ use crate::wallet::store::gen_validator_keys; use crate::wallet::{read_and_confirm_encryption_password, CliWalletUtils}; /// Validator pre-genesis wallet file name -const VALIDATOR_FILE_NAME: &str = "wallet.toml"; +const VALIDATOR_FILE_NAME: &str = "validator-wallet.toml"; /// Get the path to the validator pre-genesis wallet store. pub fn validator_file_name(store_dir: impl AsRef) -> PathBuf { diff --git a/apps/src/lib/wallet/store.rs b/apps/src/lib/wallet/store.rs index 62eae8ac0e..b358c886b1 100644 --- a/apps/src/lib/wallet/store.rs +++ b/apps/src/lib/wallet/store.rs @@ -1,20 +1,11 @@ use std::path::{Path, PathBuf}; -#[cfg(not(feature = "dev"))] -use std::str::FromStr; use ark_std::rand::prelude::*; use ark_std::rand::SeedableRng; -#[cfg(not(feature = "dev"))] -use namada::types::address::Address; use namada::types::key::*; use namada::types::transaction::EllipticCurve; -#[cfg(not(feature = "dev"))] -use namada_sdk::wallet::store::AddressVpType; -#[cfg(feature = "dev")] -use namada_sdk::wallet::StoredKeypair; use namada_sdk::wallet::{gen_sk_rng, LoadStoreError, Store, ValidatorKeys}; -use crate::config::genesis::genesis_config::GenesisConfig; use crate::wallet::CliWalletUtils; /// Wallet file name @@ -34,28 +25,6 @@ pub fn load_or_new(store_dir: &Path) -> Result { }) } -/// Load the store file or create a new one with the default addresses from -/// the genesis file, if not found. -pub fn load_or_new_from_genesis( - store_dir: &Path, - genesis_cfg: GenesisConfig, -) -> Result { - load(store_dir).or_else(|_| { - #[cfg(not(feature = "dev"))] - let store = new(genesis_cfg); - #[cfg(feature = "dev")] - let store = { - // The function is unused in dev - let _ = genesis_cfg; - new() - }; - let mut wallet = CliWalletUtils::new(store_dir.to_path_buf()); - *wallet.store_mut() = store; - wallet.save()?; - Ok(wallet.into()) - }) -} - /// Attempt to load the store file. pub fn load(store_dir: &Path) -> Result { let mut wallet = CliWalletUtils::new(store_dir.to_path_buf()); @@ -63,57 +32,6 @@ pub fn load(store_dir: &Path) -> Result { Ok(wallet.into()) } -/// Add addresses from a genesis configuration. -#[cfg(not(feature = "dev"))] -pub fn add_genesis_addresses(store: &mut Store, genesis: GenesisConfig) { - for (alias, addr) in - super::defaults::addresses_from_genesis(genesis.clone()) - { - store.insert_address::(alias, addr, true); - } - for (alias, token) in &genesis.token { - if let Some(address) = token.address.as_ref() { - match Address::from_str(address) { - Ok(address) => { - store.add_vp_type_to_address(AddressVpType::Token, address) - } - Err(_) => { - tracing::error!( - "Weird address for token {alias}: {address}" - ) - } - } - } - } -} - -#[cfg(not(feature = "dev"))] -fn new(genesis: GenesisConfig) -> Store { - let mut store = Store::default(); - add_genesis_addresses(&mut store, genesis); - store -} - -#[cfg(feature = "dev")] -fn new() -> Store { - let mut store = Store::default(); - // Pre-load the default keys without encryption - let no_password = None; - for (alias, keypair) in super::defaults::keys() { - let pkh: PublicKeyHash = (&keypair.ref_to()).into(); - store.insert_keypair::( - alias, - StoredKeypair::new(keypair, no_password.clone()).0, - pkh, - true, - ); - } - for (alias, addr) in super::defaults::addresses() { - store.insert_address::(alias, addr, true); - } - store -} - /// Generate keypair for signing protocol txs and for the DKG /// A protocol keypair may be optionally provided /// @@ -143,7 +61,7 @@ pub fn gen_validator_keys( } } -#[cfg(all(test, feature = "dev"))] +#[cfg(test)] mod test_wallet { use namada::types::address::Address; @@ -151,7 +69,7 @@ mod test_wallet { #[test] fn test_toml_roundtrip_ed25519() { - let mut store = new(); + let mut store = Store::default(); let validator_keys = gen_validator_keys(None, None, SchemeType::Ed25519); store.add_validator_data( @@ -164,7 +82,7 @@ mod test_wallet { #[test] fn test_toml_roundtrip_secp256k1() { - let mut store = new(); + let mut store = Store::default(); let validator_keys = gen_validator_keys(None, None, SchemeType::Secp256k1); store.add_validator_data( diff --git a/apps/src/lib/wasm_loader/mod.rs b/apps/src/lib/wasm_loader/mod.rs index 2d1aa95175..fe497b1505 100644 --- a/apps/src/lib/wasm_loader/mod.rs +++ b/apps/src/lib/wasm_loader/mod.rs @@ -111,31 +111,6 @@ fn wasm_url_prefix(wasm_name: &str) -> String { /// Download all the pre-built wasms, or if they're already downloaded, verify /// their checksums. pub async fn pre_fetch_wasm(wasm_directory: impl AsRef) { - #[cfg(feature = "dev")] - { - let checksums_path = wasm_directory - .as_ref() - .join(crate::config::DEFAULT_WASM_CHECKSUMS_FILE); - // If the checksums file doesn't exists ... - if tokio::fs::canonicalize(&checksums_path).await.is_err() { - tokio::fs::create_dir_all(&wasm_directory).await.unwrap(); - // ... try to copy checksums from the Namada WASM root dir - if tokio::fs::copy( - std::env::current_dir() - .unwrap() - .join(crate::config::DEFAULT_WASM_DIR) - .join(crate::config::DEFAULT_WASM_CHECKSUMS_FILE), - &checksums_path, - ) - .await - .is_ok() - { - tracing::info!("WASM checksums copied from WASM root dir."); - return; - } - } - } - // load json with wasm hashes let checksums = Checksums::read_checksums_async(&wasm_directory).await; @@ -166,26 +141,6 @@ pub async fn pre_fetch_wasm(wasm_directory: impl AsRef) { &derived_name, &full_name ); - #[cfg(feature = "dev")] - { - // try to copy built file from the Namada WASM root dir - if tokio::fs::copy( - std::env::current_dir() - .unwrap() - .join(crate::config::DEFAULT_WASM_DIR) - .join(&full_name), - wasm_directory.join(&full_name), - ) - .await - .is_ok() - { - tracing::info!( - "File {} copied from WASM root dir.", - full_name - ); - return; - } - } let url = wasm_url_prefix(&full_name); match download_wasm(url).await { @@ -209,28 +164,6 @@ pub async fn pre_fetch_wasm(wasm_directory: impl AsRef) { // if the doesn't file exist, download it. Err(err) => match err.kind() { std::io::ErrorKind::NotFound => { - #[cfg(feature = "dev")] - { - // try to copy built file from the Namada WASM root - // dir - if tokio::fs::copy( - std::env::current_dir() - .unwrap() - .join(crate::config::DEFAULT_WASM_DIR) - .join(&full_name), - wasm_directory.join(&full_name), - ) - .await - .is_ok() - { - tracing::info!( - "File {} copied from WASM root dir.", - full_name - ); - return; - } - } - let url = wasm_url_prefix(&full_name); match download_wasm(url).await { Ok(bytes) => { diff --git a/benches/Cargo.toml b/benches/Cargo.toml index 0e72e98a4b..0aa1688a79 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -41,7 +41,7 @@ path = "host_env.rs" [dev-dependencies] namada = { path = "../shared", features = ["testing"] } -namada_apps = { path = "../apps", features = ["testing"] } +namada_apps = { path = "../apps", features = ["benches"] } borsh.workspace = true borsh-ext.workspace = true criterion = { version = "0.5", features = ["html_reports"] } diff --git a/benches/native_vps.rs b/benches/native_vps.rs index 6a5d8e52ae..eecfbd6a32 100644 --- a/benches/native_vps.rs +++ b/benches/native_vps.rs @@ -25,6 +25,7 @@ use namada::ledger::native_vp::ibc::Ibc; use namada::ledger::native_vp::multitoken::MultitokenVp; use namada::ledger::native_vp::{Ctx, NativeVp}; use namada::ledger::storage_api::StorageRead; +use namada::proof_of_stake; use namada::proto::{Code, Section}; use namada::types::address::InternalAddress; use namada::types::storage::{Epoch, TxIndex}; @@ -88,7 +89,10 @@ fn governance(c: &mut Criterion) { } "minimal_proposal" => { let content_section = Section::ExtraData(Code::new(vec![])); - let voting_start_epoch = Epoch(25); + let params = + proof_of_stake::read_pos_params(&shell.wl_storage).unwrap(); + let voting_start_epoch = + Epoch(2 + params.pipeline_len + params.unbonding_len); // Must start after current epoch debug_assert_eq!( shell.wl_storage.get_block_epoch().unwrap().next(), @@ -102,8 +106,8 @@ fn governance(c: &mut Criterion) { author: defaults::albert_address(), r#type: ProposalType::Default(None), voting_start_epoch, - voting_end_epoch: 28.into(), - grace_epoch: 34.into(), + voting_end_epoch: voting_start_epoch + 3_u64, + grace_epoch: voting_start_epoch + 9_u64, }, None, Some(vec![content_section]), @@ -135,7 +139,10 @@ fn governance(c: &mut Criterion) { let wasm_code_section = Section::ExtraData(Code::new(vec![0; max_code_size as _])); - let voting_start_epoch = Epoch(25); + let params = + proof_of_stake::read_pos_params(&shell.wl_storage).unwrap(); + let voting_start_epoch = + Epoch(2 + params.pipeline_len + params.unbonding_len); // Must start after current epoch debug_assert_eq!( shell.wl_storage.get_block_epoch().unwrap().next(), @@ -151,8 +158,8 @@ fn governance(c: &mut Criterion) { wasm_code_section.get_hash(), )), voting_start_epoch, - voting_end_epoch: 28.into(), - grace_epoch: 34.into(), + voting_end_epoch: voting_start_epoch + 3_u64, + grace_epoch: voting_start_epoch + 9_u64, }, None, Some(vec![content_section, wasm_code_section]), diff --git a/core/src/ledger/inflation.rs b/core/src/ledger/inflation.rs index 3e1902a445..477eb7ade9 100644 --- a/core/src/ledger/inflation.rs +++ b/core/src/ledger/inflation.rs @@ -73,7 +73,11 @@ impl RewardsController { .expect("Should not fail to convert Uint to Dec"); let epochs_py: Dec = epochs_per_year.into(); - let locked_ratio = locked / total; + let locked_ratio = if total.is_zero() { + Dec::one() + } else { + locked / total + }; let max_inflation = total_native * max_reward_rate / epochs_py; let p_gain = p_gain_nom * max_inflation; let d_gain = d_gain_nom * max_inflation; diff --git a/core/src/ledger/pgf/parameters.rs b/core/src/ledger/pgf/parameters.rs index 6319ec33d0..1d45ea79d2 100644 --- a/core/src/ledger/pgf/parameters.rs +++ b/core/src/ledger/pgf/parameters.rs @@ -1,6 +1,7 @@ use std::collections::BTreeSet; use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{Deserialize, Serialize}; use super::storage::keys as pgf_storage; use super::storage::steward::StewardDetail; @@ -18,6 +19,8 @@ use crate::types::dec::Dec; Hash, BorshSerialize, BorshDeserialize, + Serialize, + Deserialize, )] /// Pgf parameter structure pub struct PgfParameters { diff --git a/core/src/ledger/storage/masp_conversions.rs b/core/src/ledger/storage/masp_conversions.rs index 830068220e..5cbbca570a 100644 --- a/core/src/ledger/storage/masp_conversions.rs +++ b/core/src/ledger/storage/masp_conversions.rs @@ -27,6 +27,8 @@ pub struct ConversionState { pub normed_inflation: Option, /// The tree currently containing all the conversions pub tree: FrozenCommitmentTree, + /// A map from token alias to actual address. + pub tokens: BTreeMap, /// Map assets to their latest conversion and position in Merkle tree #[allow(clippy::type_complexity)] pub assets: BTreeMap< @@ -215,7 +217,24 @@ where let key_prefix: storage::Key = masp_addr.to_db_key().into(); let tokens = address::tokens(); - let mut masp_reward_keys: Vec<_> = tokens.into_keys().collect(); + let mut masp_reward_keys: Vec<_> = tokens + .into_keys() + .map(|k| { + wl_storage + .storage + .conversion_state + .tokens + .get(k) + .unwrap_or_else(|| { + panic!( + "Could not find token alias {} in MASP conversion \ + state.", + k + ) + }) + .clone() + }) + .collect(); // Put the native rewards first because other inflation computations depend // on it let native_token = wl_storage.storage.native_token.clone(); @@ -293,7 +312,7 @@ where "MASP reward for {} assumed to be 0 because the \ computed value is too large. Please check the \ inflation parameters.", - *addr + addr ); *normed_inflation }); @@ -339,7 +358,7 @@ where "MASP reward for {} assumed to be 0 because the \ computed value is too large. Please check the \ inflation parameters.", - *addr + addr ); 0u128 }); diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index 4f506a61be..b484246228 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -484,7 +484,7 @@ where // Rebuild Merkle tree self.block.tree = MerkleTree::new(merkle_tree_stores) .or_else(|_| self.get_merkle_tree(height))?; - if self.last_epoch.0 > 0 { + if self.block.height.0 > 0 { // The derived conversions will be placed in MASP address space let masp_addr = masp(); let key_prefix: Key = masp_addr.to_db_key().into(); @@ -493,13 +493,24 @@ where let state_key = key_prefix .push(&(token::CONVERSION_KEY_PREFIX.to_owned())) .map_err(Error::KeyError)?; - self.conversion_state = types::decode( + let ConversionState { + normed_inflation, + tree, + tokens, + assets, + } = types::decode( self.read(&state_key) .expect("unable to read conversion state") .0 .expect("unable to find conversion state"), ) - .expect("unable to decode conversion state") + .expect("unable to decode conversion state"); + self.conversion_state.tokens = tokens; + if self.last_epoch.0 > 0 { + self.conversion_state.normed_inflation = normed_inflation; + self.conversion_state.tree = tree; + self.conversion_state.assets = assets; + } } #[cfg(feature = "ferveo-tpke")] { diff --git a/core/src/ledger/storage_api/pgf.rs b/core/src/ledger/storage_api/pgf.rs index b456cef0f9..91cc17b71e 100644 --- a/core/src/ledger/storage_api/pgf.rs +++ b/core/src/ledger/storage_api/pgf.rs @@ -90,10 +90,10 @@ where let pgf_inflation_rate: Dec = storage .read(&pgf_inflation_rate_key)? - .expect("Parameter should be definied."); + .expect("Parameter should be defined."); let stewards_inflation_rate: Dec = storage .read(&stewards_inflation_rate_key)? - .expect("Parameter should be definied."); + .expect("Parameter should be defined."); Ok(PgfParameters { pgf_inflation_rate, diff --git a/core/src/proto/mod.rs b/core/src/proto/mod.rs index 9dfe32e644..ecf8606008 100644 --- a/core/src/proto/mod.rs +++ b/core/src/proto/mod.rs @@ -4,8 +4,9 @@ pub mod generated; mod types; pub use types::{ - Code, Commitment, CompressedSignature, Data, Dkg, Error, Header, - MaspBuilder, Section, Signable, SignableEthMessage, Signature, + standalone_signature, verify_standalone_sig, Code, Commitment, + CompressedSignature, Data, Dkg, Error, Header, MaspBuilder, Section, + SerializeWithBorsh, Signable, SignableEthMessage, Signature, SignatureIndex, Signed, Signer, Tx, TxError, }; diff --git a/core/src/proto/types.rs b/core/src/proto/types.rs index 2702d4285e..b1d2b40cb5 100644 --- a/core/src/proto/types.rs +++ b/core/src/proto/types.rs @@ -246,6 +246,30 @@ impl> Signed { } } +/// Get a signature for data +pub fn standalone_signature>( + keypair: &common::SecretKey, + data: &T, +) -> common::Signature { + let to_sign = S::as_signable(data); + common::SigScheme::sign_with_hasher::(keypair, to_sign) +} + +/// Verify that the input data has been signed by the secret key +/// counterpart of the given public key. +pub fn verify_standalone_sig>( + data: &T, + pk: &common::PublicKey, + sig: &common::Signature, +) -> std::result::Result<(), VerifySigError> { + let signed_data = S::as_signable(data); + common::SigScheme::verify_signature_with_hasher::( + pk, + &signed_data, + sig, + ) +} + /// A section representing transaction data #[derive( Clone, diff --git a/core/src/types/address.rs b/core/src/types/address.rs index f7c63bb470..608e723b4d 100644 --- a/core/src/types/address.rs +++ b/core/src/types/address.rs @@ -7,31 +7,24 @@ use std::hash::Hash; use std::io::ErrorKind; use std::str::FromStr; -use bech32::{self, FromBase32, ToBase32, Variant}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use borsh_ext::BorshSerializeExt; use data_encoding::HEXUPPER; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; -use thiserror::Error; use crate::ibc::Signer; +use crate::impl_display_and_from_str_via_format; use crate::types::ethereum_events::EthAddress; -use crate::types::key; use crate::types::key::PublicKeyHash; use crate::types::token::Denomination; +use crate::types::{key, string_encoding}; /// The length of an established [`Address`] encoded with Borsh. pub const ESTABLISHED_ADDRESS_BYTES_LEN: usize = 21; /// The length of [`Address`] encoded with Bech32m. -pub const ADDRESS_LEN: usize = 79 + ADDRESS_HRP.len(); - -/// human-readable part of Bech32m encoded address -// TODO use "a" for live network -const ADDRESS_HRP: &str = "atest"; -/// We're using "Bech32m" variant -pub const BECH32M_VARIANT: bech32::Variant = Variant::Bech32m; +pub const ADDRESS_LEN: usize = 79 + string_encoding::hrp_len::
(); /// Length of a hash of an address as a hexadecimal string pub(crate) const HASH_HEX_LEN: usize = 40; @@ -89,6 +82,12 @@ mod internal { "ano::Pgf "; } +/// Error from decoding address from string +pub type DecodeError = string_encoding::DecodeError; + +/// Result of decoding address from string +pub type Result = std::result::Result; + /// Fixed-length address strings prefix for established addresses. const PREFIX_ESTABLISHED: &str = "est"; /// Fixed-length address strings prefix for implicit addresses. @@ -102,26 +101,6 @@ const PREFIX_ETH: &str = "eth"; /// Fixed-length address strings prefix for Non-Usable-Token addresses. const PREFIX_NUT: &str = "nut"; -#[allow(missing_docs)] -#[derive(Error, Debug, PartialEq, Eq, Clone)] -pub enum DecodeError { - #[error("Error decoding address from Bech32m: {0}")] - DecodeBech32(bech32::Error), - #[error("Error decoding address from base32: {0}")] - DecodeBase32(bech32::Error), - #[error("Unexpected Bech32m human-readable part {0}, expected {1}")] - UnexpectedBech32Prefix(String, String), - #[error("Unexpected Bech32m variant {0:?}, expected {BECH32M_VARIANT:?}")] - UnexpectedBech32Variant(bech32::Variant), - #[error("Invalid address encoding: {0}, {1}")] - InvalidInnerEncoding(ErrorKind, String), - #[error("Invalid address encoding")] - InvalidInnerEncodingStr(String), -} - -/// Result of a function that may fail -pub type Result = std::result::Result; - /// An account's address #[derive( Clone, BorshSerialize, BorshDeserialize, BorshSchema, PartialEq, Eq, Hash, @@ -153,33 +132,12 @@ impl Ord for Address { impl Address { /// Encode an address with Bech32m encoding pub fn encode(&self) -> String { - let bytes = self.to_fixed_len_string(); - bech32::encode(ADDRESS_HRP, bytes.to_base32(), BECH32M_VARIANT) - .unwrap_or_else(|_| { - panic!( - "The human-readable part {} should never cause a failure", - ADDRESS_HRP - ) - }) + string_encoding::Format::encode(self) } /// Decode an address from Bech32m encoding pub fn decode(string: impl AsRef) -> Result { - let (prefix, hash_base32, variant) = bech32::decode(string.as_ref()) - .map_err(DecodeError::DecodeBech32)?; - if prefix != ADDRESS_HRP { - return Err(DecodeError::UnexpectedBech32Prefix( - prefix, - ADDRESS_HRP.into(), - )); - } - match variant { - BECH32M_VARIANT => {} - _ => return Err(DecodeError::UnexpectedBech32Variant(variant)), - } - let bytes: Vec = FromBase32::from_base32(&hash_base32) - .map_err(DecodeError::DecodeBase32)?; - Self::try_from_fixed_len_string(&mut &bytes[..]) + string_encoding::Format::decode(string) } /// Try to get a raw hash of an address, only defined for established and @@ -199,7 +157,7 @@ impl Address { } /// Convert an address to a fixed length 7-bit ascii string bytes - fn to_fixed_len_string(&self) -> Vec { + pub fn to_fixed_len_string(&self) -> Vec { let mut string = match self { Address::Established(EstablishedAddress { hash }) => { // The bech32m's data is a hex of the first 40 chars of the hash @@ -276,7 +234,7 @@ impl Address { let raw = HEXUPPER.decode(hash.as_bytes()).map_err(|e| { DecodeError::InvalidInnerEncoding( - std::io::ErrorKind::InvalidInput, + ErrorKind::InvalidInput, e.to_string(), ) })?; @@ -394,6 +352,20 @@ impl Address { } } +impl string_encoding::Format for Address { + const HRP: &'static str = string_encoding::ADDRESS_HRP; + + fn to_bytes(&self) -> Vec { + Self::to_fixed_len_string(self) + } + + fn decode_bytes(bytes: &[u8]) -> Result { + Self::try_from_fixed_len_string(&mut &bytes[..]) + } +} + +impl_display_and_from_str_via_format!(Address); + impl serde::Serialize for Address { fn serialize( &self, @@ -418,26 +390,12 @@ impl<'de> serde::Deserialize<'de> for Address { } } -impl Display for Address { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.encode()) - } -} - impl Debug for Address { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.pretty_fmt(f) } } -impl FromStr for Address { - type Err = DecodeError; - - fn from_str(s: &str) -> Result { - Address::decode(s) - } -} - /// for IBC signer impl TryFrom for Address { type Error = DecodeError; @@ -478,7 +436,17 @@ pub struct EstablishedAddress { } /// A generator of established addresses -#[derive(Debug, Clone, PartialEq, BorshSerialize, BorshDeserialize)] +#[derive( + Debug, + Default, + Clone, + PartialEq, + Eq, + BorshSerialize, + BorshDeserialize, + Serialize, + Deserialize, +)] pub struct EstablishedAddressGen { last_hash: [u8; SHA_HASH_LEN], } @@ -603,6 +571,20 @@ impl Display for InternalAddress { } } +impl InternalAddress { + /// Certain internal addresses have reserved aliases. + pub fn try_from_alias(alias: &str) -> Option { + match alias { + "pos" => Some(InternalAddress::PoS), + "ibc" => Some(InternalAddress::Ibc), + "ethbridge" => Some(InternalAddress::EthBridge), + "bridgepool" => Some(InternalAddress::EthBridgePool), + "governance" => Some(InternalAddress::Governance), + _ => None, + } + } +} + /// Temporary helper for testing pub fn nam() -> Address { Address::decode("atest1v4ehgw36x3prswzxggunzv6pxqmnvdj9xvcyzvpsggeyvs3cg9qnywf589qnwvfsg5erg3fkl09rg5").expect("The token address decoding shouldn't fail") @@ -666,15 +648,15 @@ pub const fn wnam() -> EthAddress { /// Temporary helper for testing, a hash map of tokens addresses with their /// informal currency codes and number of decimal places. -pub fn tokens() -> HashMap { +pub fn tokens() -> HashMap<&'static str, Denomination> { vec![ - (nam(), ("NAM", 6.into())), - (btc(), ("BTC", 8.into())), - (eth(), ("ETH", 18.into())), - (dot(), ("DOT", 10.into())), - (schnitzel(), ("Schnitzel", 6.into())), - (apfel(), ("Apfel", 6.into())), - (kartoffel(), ("Kartoffel", 6.into())), + ("nam", 6.into()), + ("btc", 8.into()), + ("eth", 18.into()), + ("dot", 10.into()), + ("schnitzel", 6.into()), + ("apfel", 6.into()), + ("kartoffel", 6.into()), ] .into_iter() .collect() diff --git a/core/src/types/chain.rs b/core/src/types/chain.rs index 43977d8812..47f2af8146 100644 --- a/core/src/types/chain.rs +++ b/core/src/types/chain.rs @@ -176,12 +176,7 @@ impl ProposalBytes { } } -/// Development default chain ID. Must be [`CHAIN_ID_LENGTH`] long. -#[cfg(feature = "dev")] -pub const DEFAULT_CHAIN_ID: &str = "namada-devchain.00000000000000"; - /// Release default chain ID. Must be [`CHAIN_ID_LENGTH`] long. -#[cfg(not(feature = "dev"))] pub const DEFAULT_CHAIN_ID: &str = "namada-internal.00000000000000"; /// Chain ID diff --git a/core/src/types/dec.rs b/core/src/types/dec.rs index 40494ffad0..406144a0df 100644 --- a/core/src/types/dec.rs +++ b/core/src/types/dec.rs @@ -170,6 +170,20 @@ impl Dec { pub fn is_negative(&self) -> bool { self.0.is_negative() } + + /// Return the integer value of a [`Dec`] by rounding up. + pub fn ceil(&self) -> I256 { + if self.0.is_negative() { + self.to_i256() + } else { + let floor = self.to_i256(); + if (*self - Dec(floor)).is_zero() { + floor + } else { + floor + I256::one() + } + } + } } impl FromStr for Dec { @@ -178,7 +192,7 @@ impl FromStr for Dec { fn from_str(s: &str) -> Result { let ((large, small), is_neg) = if let Some(strip) = s.strip_prefix('-') { - (strip.split_once('.').unwrap_or((s, "0")), true) + (strip.split_once('.').unwrap_or((strip, "0")), true) } else { (s.split_once('.').unwrap_or((s, "0")), false) }; @@ -277,6 +291,12 @@ impl From for Dec { } } +impl From for Dec { + fn from(num: i32) -> Self { + Self::from(num as i128) + } +} + impl TryFrom for Dec { type Error = Box; @@ -615,8 +635,6 @@ mod test_dec { assert!(Dec::from_str("0.").is_err()); // Test that multiple decimal points get caught assert!(Dec::from_str("1.2.3").is_err()); - // Test that negative numbers are rejected - assert!(Dec::from_str("-1").is_err()); // Test that non-numerics are caught assert!(Dec::from_str("DEADBEEF.12").is_err()); assert!(Dec::from_str("23.DEADBEEF").is_err()); @@ -644,6 +662,7 @@ mod test_dec { ); } + /// Test that ordering of [`Dec`] values using more than 64 bits works. #[test] fn test_ordering() { let smaller = Dec::from_str("6483947304.195066085701").unwrap(); @@ -651,6 +670,21 @@ mod test_dec { assert!(smaller < larger); } + /// Test that taking the ceiling of a [`Dec`] works. + #[test] + fn test_ceiling() { + let neg = Dec::from_str("-2.4").expect("Test failed"); + assert_eq!( + neg.ceil(), + Dec::from_str("-2").expect("Test failed").to_i256() + ); + let pos = Dec::from_str("2.4").expect("Test failed"); + assert_eq!( + pos.ceil(), + Dec::from_str("3").expect("Test failed").to_i256() + ); + } + #[test] fn test_dec_display() { let num = Dec::from_str("14000.0000").unwrap(); diff --git a/core/src/types/key/common.rs b/core/src/types/key/common.rs index 9ca0bdaffc..33f300d884 100644 --- a/core/src/types/key/common.rs +++ b/core/src/types/key/common.rs @@ -17,8 +17,10 @@ use super::{ ParseSignatureError, RefTo, SchemeType, SigScheme as SigSchemeTrait, VerifySigError, }; +use crate::impl_display_and_from_str_via_format; use crate::types::ethereum_events::EthAddress; use crate::types::key::{SignableBytes, StorageHasher}; +use crate::types::string_encoding; /// Public key #[derive( @@ -71,24 +73,26 @@ impl super::PublicKey for PublicKey { } } -impl Display for PublicKey { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}", HEXLOWER.encode(&self.serialize_to_vec())) - } -} +/// String decoding error +pub type DecodeError = string_encoding::DecodeError; -impl FromStr for PublicKey { - type Err = ParsePublicKeyError; +impl string_encoding::Format for PublicKey { + const HRP: &'static str = string_encoding::COMMON_PK_HRP; - fn from_str(str: &str) -> Result { - let vec = HEXLOWER - .decode(str.as_ref()) - .map_err(ParsePublicKeyError::InvalidHex)?; - Self::try_from_slice(vec.as_slice()) - .map_err(ParsePublicKeyError::InvalidEncoding) + fn to_bytes(&self) -> Vec { + self.serialize_to_vec() + } + + fn decode_bytes( + bytes: &[u8], + ) -> Result { + BorshDeserialize::try_from_slice(bytes) + .map_err(DecodeError::InvalidBytes) } } +impl_display_and_from_str_via_format!(PublicKey); + #[allow(missing_docs)] #[derive(Error, Debug)] pub enum EthAddressConvError { @@ -246,6 +250,23 @@ pub enum Signature { Secp256k1(secp256k1::Signature), } +impl string_encoding::Format for Signature { + const HRP: &'static str = string_encoding::COMMON_SIG_HRP; + + fn to_bytes(&self) -> Vec { + self.serialize_to_vec() + } + + fn decode_bytes( + bytes: &[u8], + ) -> Result { + BorshDeserialize::try_from_slice(bytes) + .map_err(DecodeError::InvalidBytes) + } +} + +impl_display_and_from_str_via_format!(Signature); + impl From for Signature { fn from(sig: ed25519::Signature) -> Self { Signature::Ed25519(sig) @@ -370,3 +391,22 @@ impl super::SigScheme for SigScheme { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::types::key::ed25519; + + /// Run `cargo test gen_ed25519_keypair -- --nocapture` to generate a + /// new ed25519 keypair wrapped in `common` key types. + #[test] + fn gen_ed25519_keypair() { + let secret_key = + SecretKey::Ed25519(crate::types::key::testing::gen_keypair::< + ed25519::SigScheme, + >()); + let public_key = secret_key.to_public(); + println!("Public key: {}", public_key); + println!("Secret key: {}", secret_key); + } +} diff --git a/core/src/types/key/dkg_session_keys.rs b/core/src/types/key/dkg_session_keys.rs index ccca82aeba..07177f5ec6 100644 --- a/core/src/types/key/dkg_session_keys.rs +++ b/core/src/types/key/dkg_session_keys.rs @@ -2,19 +2,17 @@ use std::cmp::Ordering; use std::collections::BTreeMap; -use std::fmt::Display; use std::io::{Error, ErrorKind, Read}; -use std::str::FromStr; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use borsh_ext::BorshSerializeExt; -use data_encoding::HEXLOWER; use serde::{Deserialize, Serialize}; +use crate::impl_display_and_from_str_via_format; use crate::types::address::Address; -use crate::types::key::ParsePublicKeyError; use crate::types::storage::{DbKeySeg, Key, KeySeg}; +use crate::types::string_encoding; use crate::types::transaction::EllipticCurve; /// A keypair used in the DKG protocol @@ -140,25 +138,23 @@ impl BorshSchema for DkgPublicKey { } } -impl Display for DkgPublicKey { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let vec = self.serialize_to_vec(); - write!(f, "{}", HEXLOWER.encode(&vec)) - } -} +impl string_encoding::Format for DkgPublicKey { + const HRP: &'static str = string_encoding::DKG_PK_HRP; -impl FromStr for DkgPublicKey { - type Err = ParsePublicKeyError; + fn to_bytes(&self) -> Vec { + self.serialize_to_vec() + } - fn from_str(s: &str) -> Result { - let vec = HEXLOWER - .decode(s.as_ref()) - .map_err(ParsePublicKeyError::InvalidHex)?; - BorshDeserialize::try_from_slice(&vec) - .map_err(ParsePublicKeyError::InvalidEncoding) + fn decode_bytes( + bytes: &[u8], + ) -> Result { + BorshDeserialize::try_from_slice(bytes) + .map_err(string_encoding::DecodeError::InvalidBytes) } } +impl_display_and_from_str_via_format!(DkgPublicKey); + /// Obtain a storage key for user's public dkg session key. pub fn dkg_pk_key(owner: &Address) -> Key { Key::from(owner.to_db_key()) diff --git a/core/src/types/key/mod.rs b/core/src/types/key/mod.rs index f6d226e2f4..52af46d5dc 100644 --- a/core/src/types/key/mod.rs +++ b/core/src/types/key/mod.rs @@ -238,7 +238,6 @@ pub trait PublicKey: + Display + Debug + PartialOrd - + FromStr + Hash + Send + Sync diff --git a/core/src/types/masp.rs b/core/src/types/masp.rs index 9083852a81..a7cc6be9f7 100644 --- a/core/src/types/masp.rs +++ b/core/src/types/masp.rs @@ -1,7 +1,7 @@ //! MASP types use std::fmt::Display; -use std::io::ErrorKind; +use std::io::{Error, ErrorKind}; use std::str::FromStr; use bech32::{FromBase32, ToBase32}; @@ -9,17 +9,14 @@ use borsh::{BorshDeserialize, BorshSerialize}; use borsh_ext::BorshSerializeExt; use sha2::{Digest, Sha256}; -use crate::types::address::{ - masp, Address, DecodeError, BECH32M_VARIANT, HASH_HEX_LEN, +use crate::impl_display_and_from_str_via_format; +use crate::types::address::{masp, Address, DecodeError, HASH_HEX_LEN}; +use crate::types::string_encoding::{ + self, BECH32M_VARIANT, MASP_EXT_FULL_VIEWING_KEY_HRP, + MASP_EXT_SPENDING_KEY_HRP, MASP_PAYMENT_ADDRESS_HRP, + MASP_PINNED_PAYMENT_ADDRESS_HRP, }; -/// human-readable part of Bech32m encoded address -// TODO remove "test" suffix for live network -const EXT_FULL_VIEWING_KEY_HRP: &str = "xfvktest"; -const PAYMENT_ADDRESS_HRP: &str = "patest"; -const PINNED_PAYMENT_ADDRESS_HRP: &str = "ppatest"; -const EXT_SPENDING_KEY_HRP: &str = "xsktest"; - /// Wrapper for masp_primitive's FullViewingKey #[derive( Clone, @@ -35,51 +32,103 @@ const EXT_SPENDING_KEY_HRP: &str = "xsktest"; )] pub struct ExtendedViewingKey(masp_primitives::zip32::ExtendedFullViewingKey); -impl Display for ExtendedViewingKey { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl ExtendedViewingKey { + /// Encode `Self` to bytes + pub fn to_bytes(&self) -> Vec { let mut bytes = [0; 169]; self.0 .write(&mut bytes[..]) .expect("should be able to serialize an ExtendedFullViewingKey"); - let encoded = bech32::encode( - EXT_FULL_VIEWING_KEY_HRP, - bytes.to_base32(), - BECH32M_VARIANT, + bytes.to_vec() + } + + /// Try to decode `Self` from bytes + pub fn decode_bytes(bytes: &[u8]) -> Result { + masp_primitives::zip32::ExtendedFullViewingKey::read(&mut &bytes[..]) + .map(Self) + } +} + +impl string_encoding::Format for ExtendedViewingKey { + const HRP: &'static str = MASP_EXT_FULL_VIEWING_KEY_HRP; + + fn to_bytes(&self) -> Vec { + self.to_bytes() + } + + fn decode_bytes( + bytes: &[u8], + ) -> Result { + Self::decode_bytes(bytes).map_err(DecodeError::InvalidBytes) + } +} + +impl_display_and_from_str_via_format!(ExtendedViewingKey); + +impl string_encoding::Format for PaymentAddress { + const HRP: &'static str = MASP_PAYMENT_ADDRESS_HRP; + + fn to_bytes(&self) -> Vec { + self.to_bytes() + } + + fn decode_bytes( + _bytes: &[u8], + ) -> Result { + unimplemented!( + "Cannot determine if the PaymentAddress is pinned from bytes. Use \ + `PaymentAddress::decode_bytes(bytes, is_pinned)` instead." ) - .unwrap_or_else(|_| { + } + + // We override `encode` because we need to determine whether the address + // is pinned from its HRP + fn encode(&self) -> String { + let hrp = if self.is_pinned() { + MASP_PINNED_PAYMENT_ADDRESS_HRP + } else { + MASP_PAYMENT_ADDRESS_HRP + }; + let base32 = self.to_bytes().to_base32(); + bech32::encode(hrp, base32, BECH32M_VARIANT).unwrap_or_else(|_| { panic!( "The human-readable part {} should never cause a failure", - EXT_FULL_VIEWING_KEY_HRP + hrp ) - }); - write!(f, "{encoded}") + }) } -} - -impl FromStr for ExtendedViewingKey { - type Err = DecodeError; - fn from_str(string: &str) -> Result { - let (prefix, base32, variant) = - bech32::decode(string).map_err(DecodeError::DecodeBech32)?; - if prefix != EXT_FULL_VIEWING_KEY_HRP { - return Err(DecodeError::UnexpectedBech32Prefix( + // We override `decode` because we need to use different HRP for pinned and + // non-pinned address + fn decode( + string: impl AsRef, + ) -> Result { + let (prefix, base32, variant) = bech32::decode(string.as_ref()) + .map_err(DecodeError::DecodeBech32)?; + let is_pinned = if prefix == MASP_PAYMENT_ADDRESS_HRP { + false + } else if prefix == MASP_PINNED_PAYMENT_ADDRESS_HRP { + true + } else { + return Err(DecodeError::UnexpectedBech32Hrp( prefix, - EXT_FULL_VIEWING_KEY_HRP.into(), + MASP_PAYMENT_ADDRESS_HRP.into(), )); - } + }; match variant { BECH32M_VARIANT => {} _ => return Err(DecodeError::UnexpectedBech32Variant(variant)), } let bytes: Vec = FromBase32::from_base32(&base32) .map_err(DecodeError::DecodeBase32)?; - masp_primitives::zip32::ExtendedFullViewingKey::read(&mut &bytes[..]) - .map_err(|op| DecodeError::InvalidInnerEncodingStr(op.to_string())) - .map(Self) + + PaymentAddress::decode_bytes(&bytes, is_pinned) + .map_err(DecodeError::InvalidBytes) } } +impl_display_and_from_str_via_format!(PaymentAddress); + impl From for masp_primitives::zip32::ExtendedFullViewingKey { @@ -154,78 +203,45 @@ impl PaymentAddress { // hex of the first 40 chars of the hash format!("{:.width$X}", hasher.finalize(), width = HASH_HEX_LEN) } -} -impl From for masp_primitives::sapling::PaymentAddress { - fn from(addr: PaymentAddress) -> Self { - addr.0 - } -} - -impl From for PaymentAddress { - fn from(addr: masp_primitives::sapling::PaymentAddress) -> Self { - Self(addr, false) - } -} - -impl Display for PaymentAddress { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let bytes = self.0.to_bytes(); - let hrp = if self.1 { - PINNED_PAYMENT_ADDRESS_HRP - } else { - PAYMENT_ADDRESS_HRP - }; - let encoded = bech32::encode(hrp, bytes.to_base32(), BECH32M_VARIANT) - .unwrap_or_else(|_| { - panic!( - "The human-readable part {} should never cause a failure", - PAYMENT_ADDRESS_HRP - ) - }); - write!(f, "{encoded}") + /// Encode `Self` to bytes + pub fn to_bytes(&self) -> Vec { + self.0.to_bytes().to_vec() } -} -impl FromStr for PaymentAddress { - type Err = DecodeError; - - fn from_str(string: &str) -> Result { - let (prefix, base32, variant) = - bech32::decode(string).map_err(DecodeError::DecodeBech32)?; - let pinned = if prefix == PAYMENT_ADDRESS_HRP { - false - } else if prefix == PINNED_PAYMENT_ADDRESS_HRP { - true - } else { - return Err(DecodeError::UnexpectedBech32Prefix( - prefix, - PAYMENT_ADDRESS_HRP.into(), - )); - }; - match variant { - BECH32M_VARIANT => {} - _ => return Err(DecodeError::UnexpectedBech32Variant(variant)), - } + /// Try to decode `Self` from bytes + pub fn decode_bytes( + bytes: &[u8], + is_pinned: bool, + ) -> Result { let addr_len_err = |_| { - DecodeError::InvalidInnerEncoding( + Error::new( ErrorKind::InvalidData, - "expected 43 bytes for the payment address".to_string(), + "expected 43 bytes for the payment address", ) }; let addr_data_err = || { - DecodeError::InvalidInnerEncoding( + Error::new( ErrorKind::InvalidData, - "invalid payment address provided".to_string(), + "invalid payment address provided", ) }; - let bytes: Vec = FromBase32::from_base32(&base32) - .map_err(DecodeError::DecodeBase32)?; - masp_primitives::sapling::PaymentAddress::from_bytes( - &bytes.try_into().map_err(addr_len_err)?, - ) - .ok_or_else(addr_data_err) - .map(|x| Self(x, pinned)) + let bytes: &[u8; 43] = &bytes.try_into().map_err(addr_len_err)?; + masp_primitives::sapling::PaymentAddress::from_bytes(bytes) + .ok_or_else(addr_data_err) + .map(|addr| Self(addr, is_pinned)) + } +} + +impl From for masp_primitives::sapling::PaymentAddress { + fn from(addr: PaymentAddress) -> Self { + addr.0 + } +} + +impl From for PaymentAddress { + fn from(addr: masp_primitives::sapling::PaymentAddress) -> Self { + Self(addr, false) } } @@ -257,51 +273,28 @@ impl<'de> serde::Deserialize<'de> for PaymentAddress { #[derive(Clone, Debug, Copy, BorshSerialize, BorshDeserialize)] pub struct ExtendedSpendingKey(masp_primitives::zip32::ExtendedSpendingKey); -impl Display for ExtendedSpendingKey { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl string_encoding::Format for ExtendedSpendingKey { + const HRP: &'static str = MASP_EXT_SPENDING_KEY_HRP; + + fn to_bytes(&self) -> Vec { let mut bytes = [0; 169]; self.0 .write(&mut &mut bytes[..]) .expect("should be able to serialize an ExtendedSpendingKey"); - let encoded = bech32::encode( - EXT_SPENDING_KEY_HRP, - bytes.to_base32(), - BECH32M_VARIANT, - ) - .unwrap_or_else(|_| { - panic!( - "The human-readable part {} should never cause a failure", - EXT_SPENDING_KEY_HRP - ) - }); - write!(f, "{encoded}") + bytes.to_vec() } -} -impl FromStr for ExtendedSpendingKey { - type Err = DecodeError; - - fn from_str(string: &str) -> Result { - let (prefix, base32, variant) = - bech32::decode(string).map_err(DecodeError::DecodeBech32)?; - if prefix != EXT_SPENDING_KEY_HRP { - return Err(DecodeError::UnexpectedBech32Prefix( - prefix, - EXT_SPENDING_KEY_HRP.into(), - )); - } - match variant { - BECH32M_VARIANT => {} - _ => return Err(DecodeError::UnexpectedBech32Variant(variant)), - } - let bytes: Vec = FromBase32::from_base32(&base32) - .map_err(DecodeError::DecodeBase32)?; + fn decode_bytes( + bytes: &[u8], + ) -> Result { masp_primitives::zip32::ExtendedSpendingKey::read(&mut &bytes[..]) .map_err(|op| DecodeError::InvalidInnerEncodingStr(op.to_string())) .map(Self) } } +impl_display_and_from_str_via_format!(ExtendedSpendingKey); + impl From for masp_primitives::zip32::ExtendedSpendingKey { fn from(key: ExtendedSpendingKey) -> Self { key.0 @@ -407,7 +400,7 @@ impl TransferTarget { /// Get the contained PaymentAddress, if any pub fn payment_address(&self) -> Option { match self { - Self::PaymentAddress(x) => Some(*x), + Self::PaymentAddress(address) => Some(*address), _ => None, } } @@ -425,7 +418,7 @@ impl Display for TransferTarget { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Address(x) => x.fmt(f), - Self::PaymentAddress(x) => x.fmt(f), + Self::PaymentAddress(address) => address.fmt(f), } } } diff --git a/core/src/types/mod.rs b/core/src/types/mod.rs index 8aee038d9b..904e005f34 100644 --- a/core/src/types/mod.rs +++ b/core/src/types/mod.rs @@ -15,6 +15,7 @@ pub mod keccak; pub mod key; pub mod masp; pub mod storage; +pub mod string_encoding; pub mod time; pub mod token; pub mod transaction; diff --git a/core/src/types/storage.rs b/core/src/types/storage.rs index a21de88cbe..5dba69ed36 100644 --- a/core/src/types/storage.rs +++ b/core/src/types/storage.rs @@ -30,7 +30,7 @@ use crate::types::time::DateTimeUtc; pub const IBC_KEY_LIMIT: usize = 240; #[allow(missing_docs)] -#[derive(Error, Debug, Clone)] +#[derive(Error, Debug)] pub enum Error { #[error("Error parsing address: {0}")] ParseAddress(address::DecodeError), diff --git a/core/src/types/string_encoding.rs b/core/src/types/string_encoding.rs new file mode 100644 index 0000000000..9cebaa8451 --- /dev/null +++ b/core/src/types/string_encoding.rs @@ -0,0 +1,231 @@ +//! Namada's standard string encoding for public types. +//! +//! We're using [bech32m](https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki), +//! a format with a human-readable, followed by base32 encoding with a limited +//! character set with checksum check. +//! +//! To use this encoding for a new type, add a HRP (human-readable part) const +//! below and use it to `impl string_encoding::Format for YourType`. + +use std::fmt::Display; +use std::io::ErrorKind; +use std::ops::Deref; +use std::str::FromStr; + +use bech32::{self, FromBase32, ToBase32, Variant}; +use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +/// We're using "Bech32m" variant +pub const BECH32M_VARIANT: bech32::Variant = Variant::Bech32m; + +// Human-readable parts of Bech32m encoding +// +// Invariant: HRPs must be unique !!! +// +// TODO: remove "test" suffix for live network +/// `Address` human-readable part +pub const ADDRESS_HRP: &str = "atest"; +/// MASP extended viewing key human-readable part +pub const MASP_EXT_FULL_VIEWING_KEY_HRP: &str = "xfvktest"; +/// MASP payment address (not pinned) human-readable part +pub const MASP_PAYMENT_ADDRESS_HRP: &str = "patest"; +/// MASP pinned payment address human-readable part +pub const MASP_PINNED_PAYMENT_ADDRESS_HRP: &str = "ppatest"; +/// MASP extended spending key human-readable part +pub const MASP_EXT_SPENDING_KEY_HRP: &str = "xsktest"; +/// `common::PublicKey` human-readable part +pub const COMMON_PK_HRP: &str = "pktest"; +/// `DkgPublicKey` human-readable part +pub const DKG_PK_HRP: &str = "dpktest"; +/// `common::Signature` human-readable part +pub const COMMON_SIG_HRP: &str = "sigtest"; + +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub enum DecodeError { + #[error("Error decoding from Bech32m: {0}")] + DecodeBech32(bech32::Error), + #[error("Error decoding from base32: {0}")] + DecodeBase32(bech32::Error), + #[error("Unexpected Bech32m human-readable part {0}, expected {1}")] + UnexpectedBech32Hrp(String, String), + #[error("Unexpected Bech32m variant {0:?}, expected {BECH32M_VARIANT:?}")] + UnexpectedBech32Variant(bech32::Variant), + #[error("Invalid address encoding: {0}, {1}")] + InvalidInnerEncoding(ErrorKind, String), + #[error("Invalid address encoding")] + InvalidInnerEncodingStr(String), + #[error("Invalid bytes: {0}")] + InvalidBytes(std::io::Error), +} + +/// Format to string with bech32m +pub trait Format: Sized { + /// Human-readable part + const HRP: &'static str; + + /// Encode `Self` to a string + fn encode(&self) -> String { + let base32 = self.to_bytes().to_base32(); + bech32::encode(Self::HRP, base32, BECH32M_VARIANT).unwrap_or_else( + |_| { + panic!( + "The human-readable part {} should never cause a failure", + Self::HRP + ) + }, + ) + } + + /// Try to decode `Self` from a string + fn decode(string: impl AsRef) -> Result { + let (hrp, hash_base32, variant) = bech32::decode(string.as_ref()) + .map_err(DecodeError::DecodeBech32)?; + if hrp != Self::HRP { + return Err(DecodeError::UnexpectedBech32Hrp( + hrp, + Self::HRP.into(), + )); + } + match variant { + BECH32M_VARIANT => {} + _ => return Err(DecodeError::UnexpectedBech32Variant(variant)), + } + let bytes: Vec = FromBase32::from_base32(&hash_base32) + .map_err(DecodeError::DecodeBase32)?; + + Self::decode_bytes(&bytes) + } + + /// Encode `Self` to bytes + fn to_bytes(&self) -> Vec; + + /// Try to decode `Self` from bytes + fn decode_bytes(bytes: &[u8]) -> Result; +} + +/// Implement [`std::fmt::Display`] and [`std::str::FromStr`] via +/// [`Format`]. +#[macro_export] +macro_rules! impl_display_and_from_str_via_format { + ($t:path) => { + impl std::fmt::Display for $t { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + $crate::types::string_encoding::Format::encode(self) + ) + } + } + + impl std::str::FromStr for $t { + type Err = $crate::types::string_encoding::DecodeError; + + fn from_str(s: &str) -> std::result::Result { + $crate::types::string_encoding::Format::decode(s) + } + } + }; +} + +/// Get the length of the human-readable part +// Not in the `Format` trait, cause functions in traits cannot be const +pub const fn hrp_len() -> usize { + T::HRP.len() +} + +/// Wrapper for `T` to serde encode via `Display` and decode via `FromStr` +#[derive( + Clone, + Debug, + Deserialize, + Serialize, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + BorshSerialize, + BorshDeserialize, +)] +#[serde(transparent)] +pub struct StringEncoded +where + T: FromStr + Display, + ::Err: Display, +{ + /// Raw value + #[serde( + serialize_with = "encode_via_display", + deserialize_with = "decode_via_from_str" + )] + pub raw: T, +} + +impl StringEncoded +where + T: FromStr + Display, + ::Err: Display, +{ + /// Wrap to make `T` string encoded + pub fn new(raw: T) -> Self { + Self { raw } + } +} + +impl Deref for StringEncoded +where + T: FromStr + Display, + ::Err: Display, +{ + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.raw + } +} + +impl Display for StringEncoded +where + T: FromStr + Display, + ::Err: Display, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.raw.fmt(f) + } +} + +impl FromStr for StringEncoded +where + T: FromStr + Display, + ::Err: Display, +{ + type Err = ::Err; + + fn from_str(s: &str) -> Result { + let raw = T::from_str(s)?; + Ok(Self { raw }) + } +} + +fn encode_via_display(val: &T, serializer: S) -> Result +where + S: serde::Serializer, + T: Display, +{ + let val_str = val.to_string(); + serde::Serialize::serialize(&val_str, serializer) +} + +fn decode_via_from_str<'de, D, T>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, + T: FromStr, + ::Err: Display, +{ + let val_str: String = serde::Deserialize::deserialize(deserializer)?; + FromStr::from_str(&val_str).map_err(serde::de::Error::custom) +} diff --git a/core/src/types/time.rs b/core/src/types/time.rs index 341a81411c..948bf6373f 100644 --- a/core/src/types/time.rs +++ b/core/src/types/time.rs @@ -10,6 +10,7 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use chrono::ParseError; pub use chrono::{DateTime, Duration, TimeZone, Utc}; +use serde::{Deserialize, Serialize}; /// Check if the given `duration` has passed since the given `start. pub fn duration_passed( @@ -30,6 +31,8 @@ pub fn duration_passed( PartialOrd, Ord, Hash, + Serialize, + Deserialize, BorshSerialize, BorshDeserialize, BorshSchema, @@ -73,6 +76,8 @@ impl Display for DurationSecs { PartialOrd, Ord, Hash, + Serialize, + Deserialize, BorshSerialize, BorshDeserialize, BorshSchema, @@ -93,8 +98,26 @@ impl From for DurationNanos { } } +impl From for std::time::Duration { + fn from(DurationNanos { secs, nanos }: DurationNanos) -> Self { + Self::new(secs, nanos) + } +} + /// An RFC 3339 timestamp (e.g., "1970-01-01T00:00:00Z"). -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +#[derive( + Clone, + Debug, + Deserialize, + Serialize, + BorshDeserialize, + BorshSerialize, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, +)] pub struct Rfc3339String(pub String); /// A duration in seconds precision. @@ -114,6 +137,9 @@ pub struct Rfc3339String(pub String); #[serde(try_from = "Rfc3339String", into = "Rfc3339String")] pub struct DateTimeUtc(pub DateTime); +/// The minimum possible DateTime. +pub const MIN_UTC: DateTimeUtc = DateTimeUtc(chrono::DateTime::::MIN_UTC); + impl Display for DateTimeUtc { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.to_rfc3339()) @@ -292,3 +318,17 @@ impl TryFrom for DateTimeUtc { Rfc3339String(t.to_rfc3339()).try_into() } } + +#[cfg(any(feature = "tendermint", feature = "tendermint-abcipp"))] +impl From for DurationNanos { + fn from(val: crate::tendermint::Timeout) -> Self { + Self::from(std::time::Duration::from(val)) + } +} + +#[cfg(any(feature = "tendermint", feature = "tendermint-abcipp"))] +impl From for crate::tendermint::Timeout { + fn from(val: DurationNanos) -> Self { + Self::from(std::time::Duration::from(val)) + } +} diff --git a/core/src/types/token.rs b/core/src/types/token.rs index 9e7c2d0ee7..372cd9a2c0 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -1,5 +1,6 @@ //! A basic fungible token +use std::cmp::Ordering; use std::fmt::Display; use std::iter::Sum; use std::ops::{Add, AddAssign, Div, Mul, Sub, SubAssign}; @@ -313,8 +314,6 @@ impl From for u8 { Hash, PartialEq, Eq, - PartialOrd, - Ord, BorshSerialize, BorshDeserialize, BorshSchema, @@ -348,6 +347,10 @@ impl DenominatedAmount { pub fn to_string_precise(&self) -> String { let decimals = self.denom.0 as usize; let mut string = self.amount.raw.to_string(); + // escape hatch if there are no decimal places + if decimals == 0 { + return string; + } if string.len() > decimals { string.insert(string.len() - decimals, '.'); } else { @@ -403,7 +406,11 @@ impl DenominatedAmount { impl Display for DenominatedAmount { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let string = self.to_string_precise(); - let string = string.trim_end_matches(&['0']); + let string = if self.denom.0 > 0 { + string.trim_end_matches(&['0']) + } else { + &string + }; let string = string.trim_end_matches(&['.']); f.write_str(string) } @@ -453,6 +460,50 @@ impl FromStr for DenominatedAmount { } } +impl PartialOrd for DenominatedAmount { + fn partial_cmp(&self, other: &Self) -> Option { + if self.denom < other.denom { + let diff = other.denom.0 - self.denom.0; + let (div, rem) = + other.amount.raw.div_mod(Uint::exp10(diff as usize)); + let div_ceil = if rem.is_zero() { + div + } else { + div + Uint::one() + }; + let ord = self.amount.raw.partial_cmp(&div_ceil); + if let Some(Ordering::Equal) = ord { + if rem.is_zero() { + Some(Ordering::Equal) + } else { + Some(Ordering::Greater) + } + } else { + ord + } + } else { + let diff = self.denom.0 - other.denom.0; + let (div, rem) = + self.amount.raw.div_mod(Uint::exp10(diff as usize)); + let div_ceil = if rem.is_zero() { + div + } else { + div + Uint::one() + }; + let ord = div_ceil.partial_cmp(&other.amount.raw); + if let Some(Ordering::Equal) = ord { + if rem.is_zero() { + Some(Ordering::Equal) + } else { + Some(Ordering::Less) + } + } else { + ord + } + } + } +} + impl serde::Serialize for Amount { fn serialize( &self, @@ -975,6 +1026,16 @@ impl Parameters { kp_gain_nom, locked_ratio_target: locked_target, } = self; + wl_storage + .write(&masp_last_inflation_key(address), Amount::zero()) + .expect( + "last inflation key for the given asset must be initialized", + ); + wl_storage + .write(&masp_last_locked_ratio_key(address), Dec::zero()) + .expect( + "last locked ratio key for the given asset must be initialized", + ); wl_storage .write(&masp_max_reward_rate_key(address), max_rate) .expect("max reward rate for the given asset must be initialized"); @@ -1216,6 +1277,13 @@ mod tests { }; assert_eq!("0.0112", amount.to_string()); assert_eq!("0.01120", amount.to_string_precise()); + + let amount = DenominatedAmount { + amount: Amount::from_uint(200, 0).expect("Test failed"), + denom: 0.into(), + }; + assert_eq!("200", amount.to_string()); + assert_eq!("200", amount.to_string_precise()); } #[test] @@ -1342,6 +1410,62 @@ mod tests { assert_eq!(two.mul_ceil(dec), one); assert_eq!(three.mul_ceil(dec), two); } + + #[test] + fn test_denominated_amt_ord() { + let denom_1 = DenominatedAmount { + amount: Amount::from_uint(15, 0).expect("Test failed"), + denom: 1.into(), + }; + let denom_2 = DenominatedAmount { + amount: Amount::from_uint(1500, 0).expect("Test failed"), + denom: 3.into(), + }; + // The psychedelic case. Partial ordering works on the underlying + // amounts but `Eq` also checks the equality of denoms. + assert_eq!( + denom_1.partial_cmp(&denom_2).expect("Test failed"), + Ordering::Equal + ); + assert_eq!( + denom_2.partial_cmp(&denom_1).expect("Test failed"), + Ordering::Equal + ); + assert_ne!(denom_1, denom_2); + + let denom_1 = DenominatedAmount { + amount: Amount::from_uint(15, 0).expect("Test failed"), + denom: 1.into(), + }; + let denom_2 = DenominatedAmount { + amount: Amount::from_uint(1501, 0).expect("Test failed"), + denom: 3.into(), + }; + assert_eq!( + denom_1.partial_cmp(&denom_2).expect("Test failed"), + Ordering::Less + ); + assert_eq!( + denom_2.partial_cmp(&denom_1).expect("Test failed"), + Ordering::Greater + ); + let denom_1 = DenominatedAmount { + amount: Amount::from_uint(15, 0).expect("Test failed"), + denom: 1.into(), + }; + let denom_2 = DenominatedAmount { + amount: Amount::from_uint(1499, 0).expect("Test failed"), + denom: 3.into(), + }; + assert_eq!( + denom_1.partial_cmp(&denom_2).expect("Test failed"), + Ordering::Greater + ); + assert_eq!( + denom_2.partial_cmp(&denom_1).expect("Test failed"), + Ordering::Less + ); + } } /// Helpers for testing with addresses. @@ -1368,50 +1492,4 @@ pub mod testing { ) -> impl Strategy { (1..=max).prop_map(|val| Amount::from_uint(val, 0).unwrap()) } - - /// init_token_storage is useful when the initialization of the network is - /// not properly made. This properly sets up the storage such that - /// inflation calculations can be ran on the token addresses. We assume - /// a total supply that may not be real - pub fn init_token_storage( - wl_storage: &mut ledger_storage::WlStorage, - epochs_per_year: u64, - ) where - D: 'static - + ledger_storage::DB - + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + ledger_storage::StorageHasher, - { - use crate::ledger::parameters::storage::get_epochs_per_year_key; - use crate::types::address::tokens; - - let tokens = tokens(); - let masp_reward_keys: Vec<_> = tokens.keys().collect(); - - wl_storage - .write(&get_epochs_per_year_key(), epochs_per_year) - .unwrap(); - let params = Parameters { - max_reward_rate: Dec::from_str("0.1").unwrap(), - kd_gain_nom: Dec::from_str("0.1").unwrap(), - kp_gain_nom: Dec::from_str("0.1").unwrap(), - locked_ratio_target: Dec::zero(), - }; - - for address in masp_reward_keys { - params.init_storage(address, wl_storage); - wl_storage - .write( - &minted_balance_key(address), - Amount::native_whole(5), // arbitrary amount - ) - .unwrap(); - wl_storage - .write(&masp_last_inflation_key(address), Amount::zero()) - .expect("inflation ought to be written"); - wl_storage - .write(&masp_last_locked_ratio_key(address), Dec::zero()) - .expect("last locked set default"); - } - } } diff --git a/core/src/types/uint.rs b/core/src/types/uint.rs index e6aeedc252..463fa34383 100644 --- a/core/src/types/uint.rs +++ b/core/src/types/uint.rs @@ -538,6 +538,11 @@ impl I256 { Self(Uint::zero()) } + /// Gives the one value of an I256 + pub fn one() -> I256 { + Self(Uint::one()) + } + /// Get a string representation of `self` as a /// native token amount. pub fn to_string_native(&self) -> String { diff --git a/ethereum_bridge/Cargo.toml b/ethereum_bridge/Cargo.toml index 40d8e6f422..08c5731e28 100644 --- a/ethereum_bridge/Cargo.toml +++ b/ethereum_bridge/Cargo.toml @@ -48,6 +48,7 @@ tracing = "0.1.30" [dev-dependencies] # Added "testing" feature. namada_core = {path = "../core", default-features = false, features = ["ferveo-tpke", "ethers-derive", "testing"]} +namada_proof_of_stake = {path = "../proof_of_stake", default-features = false, features = ["testing"]} assert_matches.workspace = true data-encoding.workspace = true ethabi.workspace = true diff --git a/ethereum_bridge/src/parameters.rs b/ethereum_bridge/src/parameters.rs index 599ef0c5f8..62d1f9a7a1 100644 --- a/ethereum_bridge/src/parameters.rs +++ b/ethereum_bridge/src/parameters.rs @@ -150,7 +150,7 @@ pub struct Contracts { BorshSerialize, BorshDeserialize, )] -pub struct EthereumBridgeConfig { +pub struct EthereumBridgeParams { /// Initial Ethereum block height when events will first be extracted from. pub eth_start_height: ethereum_structs::BlockHeight, /// Minimum number of confirmations needed to trust an Ethereum branch. @@ -163,7 +163,7 @@ pub struct EthereumBridgeConfig { pub contracts: Contracts, } -impl EthereumBridgeConfig { +impl EthereumBridgeParams { /// Initialize the Ethereum bridge parameters in storage. /// /// If these parameters are initialized, the storage subspaces @@ -248,7 +248,7 @@ impl EthereumBridgeConfig { } } -/// Subset of [`EthereumBridgeConfig`], containing only Ethereum +/// Subset of [`EthereumBridgeParams`], containing only Ethereum /// oracle specific parameters. #[derive(Clone, Debug, Eq, PartialEq)] pub struct EthereumOracleConfig { @@ -262,9 +262,9 @@ pub struct EthereumOracleConfig { pub contracts: Contracts, } -impl From for EthereumOracleConfig { - fn from(config: EthereumBridgeConfig) -> Self { - let EthereumBridgeConfig { +impl From for EthereumOracleConfig { + fn from(config: EthereumBridgeParams) -> Self { + let EthereumBridgeParams { eth_start_height, min_confirmations, contracts, @@ -374,7 +374,7 @@ mod tests { use super::*; use crate::parameters::{ - ContractVersion, Contracts, EthereumBridgeConfig, MinimumConfirmations, + ContractVersion, Contracts, EthereumBridgeParams, MinimumConfirmations, UpgradeableContract, }; @@ -383,7 +383,7 @@ mod tests { /// in any of the config structs. #[test] fn test_round_trip_toml_serde() -> Result<()> { - let config = EthereumBridgeConfig { + let config = EthereumBridgeParams { erc20_whitelist: vec![], eth_start_height: Default::default(), min_confirmations: MinimumConfirmations::default(), @@ -396,7 +396,7 @@ mod tests { }, }; let serialized = toml::to_string(&config)?; - let deserialized: EthereumBridgeConfig = toml::from_str(&serialized)?; + let deserialized: EthereumBridgeParams = toml::from_str(&serialized)?; assert_eq!(config, deserialized); Ok(()) @@ -405,7 +405,7 @@ mod tests { #[test] fn test_ethereum_bridge_config_read_write_storage() { let mut wl_storage = TestWlStorage::default(); - let config = EthereumBridgeConfig { + let config = EthereumBridgeParams { erc20_whitelist: vec![], eth_start_height: Default::default(), min_confirmations: MinimumConfirmations::default(), @@ -437,7 +437,7 @@ mod tests { #[should_panic(expected = "Could not read")] fn test_ethereum_bridge_config_storage_corrupt() { let mut wl_storage = TestWlStorage::default(); - let config = EthereumBridgeConfig { + let config = EthereumBridgeParams { erc20_whitelist: vec![], eth_start_height: Default::default(), min_confirmations: MinimumConfirmations::default(), diff --git a/ethereum_bridge/src/test_utils.rs b/ethereum_bridge/src/test_utils.rs index cc5370360d..c4d4967389 100644 --- a/ethereum_bridge/src/test_utils.rs +++ b/ethereum_bridge/src/test_utils.rs @@ -6,7 +6,6 @@ use std::num::NonZeroU64; use borsh_ext::BorshSerializeExt; use namada_core::ledger::eth_bridge::storage::bridge_pool::get_key_from_hash; use namada_core::ledger::eth_bridge::storage::whitelist; -use namada_core::ledger::governance::parameters::GovernanceParameters; use namada_core::ledger::storage::mockdb::MockDBWriteBatch; use namada_core::ledger::storage::testing::{TestStorage, TestWlStorage}; use namada_core::ledger::storage_api::token::credit_tokens; @@ -27,7 +26,7 @@ use namada_proof_of_stake::{ }; use crate::parameters::{ - ContractVersion, Contracts, EthereumBridgeConfig, MinimumConfirmations, + ContractVersion, Contracts, EthereumBridgeParams, MinimumConfirmations, UpgradeableContract, }; @@ -99,12 +98,12 @@ pub fn default_validator() -> (Address, token::Amount) { (addr, voting_power) } -/// Writes a dummy [`EthereumBridgeConfig`] to the given [`TestWlStorage`], and +/// Writes a dummy [`EthereumBridgeParams`] to the given [`TestWlStorage`], and /// returns it. pub fn bootstrap_ethereum_bridge( wl_storage: &mut TestWlStorage, -) -> EthereumBridgeConfig { - let config = EthereumBridgeConfig { +) -> EthereumBridgeParams { + let config = EthereumBridgeParams { // start with empty erc20 whitelist erc20_whitelist: vec![], eth_start_height: Default::default(), @@ -217,11 +216,9 @@ pub fn init_storage_with_validators( }) .collect(); - let gov_params = GovernanceParameters::default(); - gov_params.init_storage(wl_storage).unwrap(); - namada_proof_of_stake::init_genesis( + namada_proof_of_stake::test_utils::test_init_genesis( wl_storage, - &OwnedPosParams::default(), + OwnedPosParams::default(), validators.into_iter(), 0.into(), ) @@ -291,11 +288,12 @@ pub fn append_validators_to_storage( current_epoch, commission_rate: Dec::new(5, 2).unwrap(), max_commission_rate_change: Dec::new(1, 2).unwrap(), + offset_opt: Some(1), }) .expect("Test failed"); credit_tokens(wl_storage, &staking_token, &validator, stake) .expect("Test failed"); - bond_tokens(wl_storage, None, &validator, stake, current_epoch) + bond_tokens(wl_storage, None, &validator, stake, current_epoch, None) .expect("Test failed"); all_keys.insert(validator, keys); diff --git a/genesis/README.md b/genesis/README.md new file mode 100644 index 0000000000..83495cd996 --- /dev/null +++ b/genesis/README.md @@ -0,0 +1,153 @@ +# Genesis templates + +An example setup with a single validator used to run a localnet can be found in [localnet](localnet/README.md) directory. + +[Starter templates](starter/README.md) can be used to configure new networks. + +The required genesis templates to setup a network are: + +- [`validity-predicates.toml`](#validity-predicates) +- [`tokens.toml`](#tokens) +- [`balances.toml`](#balances) +- [`parameters.toml`](#parameters) +- [`transactions.toml`](#transactions) + +## Validity predicates + +The [validity-predicates.toml file](validity-predicates) contains definitions of WASM validity predicates, which can be used in the [tokens](#tokens), [parameters](#parameters) and [transactions.toml](#transactions) files as validity predicates of established accounts. + +## Tokens + +The [tokens.toml file](tokens.toml) contains tokens with their aliases and validity predicates. + +## Balances + +The [balances.toml file](balances.toml) contains token balances associated with the public keys. + +TODO: add shielded balances + +## Parameters + +The [parameters.toml file](parameters.toml) contains the general chain parameters, PoS and governance parameters. + +## Transactions + +The [transactions.toml file](transactions.toml) contains any transactions that can be applied at genesis. These are: + +### Genesis tx `established_account` + +An established account with some `alias`, a validity predicate `vp` and optionally a `public_key`. When a public key is used, the transaction must be [signed](#signing-genesis-txs) with it to authorize its use. + +An unsigned `established_account` tx example: + +```toml +[[established_account]] +alias = "Albert" # Aliases are case-insensitive +vp = "vp_user" +public_key = "pktest1qz0aphcsrw37j8fy742cjwhphu9jwx7esd3ad4xxtxrkwv07ff63wnnul44" +``` + +### Genesis tx `validator_account` + +A validator account with some `alias`, a validity predicate `vp`, various keys and validator variables. Public keys used in the transaction must also [sign](#signing-validator-genesis-txs) the transaction to authorize their use. + +An unsigned `validator_account` tx example: + +```toml +[[validator_account]] +alias = "validator-0" +vp = "vp_validator" +commission_rate = "0.05" +max_commission_rate_change = "0.01" +net_address = "127.0.0.1:27656" +account_key = "pktest1qzjnu45v9uvvz4shwkxrgq44l7l4ncs0ryt9mwt7973fdjvm76tgkkakjmf" +consensus_key = "pktest1qp4dcws0fthlrt69erz854efxxtxvympw9m3npy2w8rphqgxu2ufc476rgt" +protocol_key = "pktest1qqwg6uwuxn70spl9x377v0q6fzr6d29gpkdfc0tmp8uj97p5awnukkeue3k" +tendermint_node_key = "pktest1qzmajsm6a5uamaq7el4kkp6txe9jt0ld3q0jy0er7cuz0u0k2yck6ls5ppm" +dkg_key = "dpktest1vqqqqqqzlgrsdkkjc0yg842xqkffy7g2vwvx3x8389ydprz2qwncruzxr8cg8u939z4yy76wkx6uwfe7qur95yrftsd0r8lu0ayhu4zqsrkf9em3n5zpm7jkcmjtg0a24h2fa5gejvt0ywddwc6xa72f3z8czkcw9ynw66" +``` + +### Genesis tx `transfer` + +A transfer can only be applied from one of the keys used in [Balances file](#balances) as the `source`. The target may be another key or an alias of an account to be created with `established_account` or `validator_account` genesis transactions. + +An unsigned `transfer` tx example: + +```toml +[[transfer]] +token = "NAM" +source = "pktest1qz0aphcsrw37j8fy742cjwhphu9jwx7esd3ad4xxtxrkwv07ff63wnnul44" +target = "albert" +amount = 1_000_000 +``` + +### Genesis tx `bond` + +A bond may be either a self-bond when the `source` is the same as `validator` or a delegation otherwise. + +An example of an unsigned delegation `bond` tx from `established_account` with alias "albert": + +```toml +[[bond]] +source = "albert" +validator = "validator-0" # There must be a `validator_account` tx with this alias +amount = 20_000 # in native token NAM +``` + +For a delegation `bond` tx from an implicit account, one can use a public key as the source: + +```toml +[[bond]] +source = "pktest1qz0aphcsrw37j8fy742cjwhphu9jwx7esd3ad4xxtxrkwv07ff63wnnul44" +validator = "validator-0" +amount = 20_000 # in native token NAM +``` + +Note that for a delegation, the source key must have the sufficient balance assigned in the Balances file. + +An unsigned self-`bond` tx example: + +```toml +[[bond]] +source = "validator-0" +validator = "validator-0" +amount = 90_000_000 # The validator must have this amount of NAM available in account +``` + +### Signing genesis txs + +To sign genesis transactions, the data is borsh-encoded into a `Tx` `data` field. For `code` an empty vec is used and for the timestamp we use the minimum UTC timestamp. The transaction must be constructed in exactly the same way to verify the signatures, which is being done by the ledger when we're initializing the genesis. Any transaction that has invalid signature or cannot be applied for any other reason, such as insufficient funds may fail at genesis initialization and the chain will continue to be initialized without it. + +For non-validator transactions, a helper tool for producing signatures for transactions can be used with e.g.: + +```shell +namada client utils \ + sign-genesis-tx \ + --path "unsigned-tx.toml" \ + --output "signed-txs.toml" +``` + +For validator txs, see [Signing validator genesis txs](#signing-validator-genesis-txs) below. + +#### Signing validator genesis txs + +To generate validator wallet and sign validator transactions, run e.g.: + +```shell +namadac utils \ + init-genesis-validator \ + --source validator-0-key \ + --alias validator-0 \ + --net-address "127.0.0.1:27656" \ + --commission-rate 0.05 \ + --max-commission-rate-change 0.01 \ + --transfer-from-source-amount 1_000_000_000 \ + --self-bond-amount 900_000_000 \ + --unsafe-dont-encrypt +``` + +The `--source` key alias must have already have native token `NAM` in the [Balances files](#balances) and the balance must be greater than or equal to `--transfer-from-source-amount`. + +The `--self-bond-amount` must be lower than or equal to `--transfer-from-source-amount`, but we recommend to keep at least some tokens in the validator account for submitting validator transactions to be able to pay for fees and gas. + +This command will generate a validator pre-genesis wallet and transactions file containing signed `validator_account`, `transfer` and `bond` txs. diff --git a/genesis/dev.toml b/genesis/dev.toml deleted file mode 100644 index dded8358d9..0000000000 --- a/genesis/dev.toml +++ /dev/null @@ -1,247 +0,0 @@ -# Developer network -genesis_time = "2021-12-20T15:00:00.00Z" -native_token = "NAM" - -# Some tokens present at genesis. - -[token.NAM] -address = "atest1v4ehgw36x3prswzxggunzv6pxqmnvdj9xvcyzvpsggeyvs3cg9qnywf589qnwvfsg5erg3fkl09rg5" -denom = 6 -vp = "vp_token" -[token.NAM.balances] -Albert = "1000000" -"Albert.public_key" = "100" -Bertha = "1000000" -"Bertha.public_key" = "2000" -Christel = "1000000" -"Christel.public_key" = "100" -Daewon = "1000000" -Ester = "1000000" -[token.NAM.parameters] -max_reward_rate = "0.1" -kd_gain_nom = "0.1" -kp_gain_nom = "0.1" -locked_ratio_target_key = "0.6667" - -[token.BTC] -address = "atest1v4ehgw36xdzryve5gsc52veeg5cnsv2yx5eygvp38qcrvd29xy6rys6p8yc5xvp4xfpy2v694wgwcp" -denom = 8 -vp = "vp_token" -[token.BTC.balances] -Albert = "1000000" -Bertha = "1000000" -Christel = "1000000" -Daewon = "1000000" -Ester = "1000000" -[token.BTC.parameters] -max_reward_rate = "0.1" -kd_gain_nom = "0.1" -kp_gain_nom = "0.1" -locked_ratio_target_key = "0.6667" - -[token.ETH] -address = "atest1v4ehgw36xqmr2d3nx3ryvd2xxgmrq33j8qcns33sxezrgv6zxdzrydjrxveygd2yxumrsdpsf9jc2p" -denom = 18 -vp = "vp_token" -[token.ETH.balances] -Albert = "1000000" -Bertha = "1000000" -Christel = "1000000" -Daewon = "1000000" -Ester = "1000000" -[token.ETH.parameters] -max_reward_rate = "0.1" -kd_gain_nom = "0.1" -kp_gain_nom = "0.1" -locked_ratio_target_key = "0.6667" - -[token.DOT] -address = "atest1v4ehgw36gg6nvs2zgfpyxsfjgc65yv6pxy6nwwfsxgungdzrggeyzv35gveyxsjyxymyz335hur2jn" -denom = 10 -vp = "vp_token" -[token.DOT.balances] -Albert = "1000000" -Bertha = "1000000" -Christel = "1000000" -Daewon = "1000000" -Ester = "1000000" -[token.DOT.parameters] -max_reward_rate = "0.1" -kd_gain_nom = "0.1" -kp_gain_nom = "0.1" -locked_ratio_target_key = "0.6667" - -[token.Schnitzel] -address = "atest1v4ehgw36xue5xvf5xvuyzvpjx5un2v3k8qeyvd3cxdqns32p89rrxd6xx9zngvpegccnzs699rdnnt" -denom = 6 -vp = "vp_token" -[token.Schnitzel.balances] -Albert = "1000000" -Bertha = "1000000" -Christel = "1000000" -Daewon = "1000000" -Ester = "1000000" -[token.Schnitzel.parameters] -max_reward_rate = "0.1" -kd_gain_nom = "0.1" -kp_gain_nom = "0.1" -locked_ratio_target_key = "0.6667" - -[token.Apfel] -address = "atest1v4ehgw36gfryydj9g3p5zv3kg9znyd358ycnzsfcggc5gvecgc6ygs2rxv6ry3zpg4zrwdfeumqcz9" -denom = 6 -vp = "vp_token" -[token.Apfel.balances] -Albert = "1000000" -Bertha = "1000000" -Christel = "1000000" -Daewon = "1000000" -Ester = "1000000" -[token.Apfel.parameters] -max_reward_rate = "0.1" -kd_gain_nom = "0.1" -kp_gain_nom = "0.1" -locked_ratio_target_key = "0.6667" - -[token.Kartoffel] -address = "atest1v4ehgw36gep5ysecxq6nyv3jg3zygv3e89qn2vp48pryxsf4xpznvve5gvmy23fs89pryvf5a6ht90" -public_key = "" -denom = 6 -vp = "vp_token" -[token.Kartoffel.balances] -Albert = "1000000" -Bertha = "1000000" -Christel = "1000000" -Daewon = "1000000" -Ester = "1000000" -[token.Kartoffel.parameters] -max_reward_rate = "0.1" -kd_gain_nom = "0.1" -kp_gain_nom = "0.1" -locked_ratio_target_key = "0.6667" - -[established.Albert] -vp = "vp_user" - -[established.Bertha] -vp = "vp_user" - -[established.Christel] -vp = "vp_user" - -[established.masp] -address = "atest1v4ehgw36xaryysfsx5unvve4g5my2vjz89p52sjxxgenzd348yuyyv3hg3pnjs35g5unvde4ca36y5" -vp = "vp_masp" - -[implicit.Daewon] - -[implicit.Ester] - -# Wasm VP definitions - -# Wasm VP definitions - -# Implicit VP -[wasm.vp_implicit] -filename = "vp_implicit.wasm" - -# Default user VP in established accounts -[wasm.vp_user] -filename = "vp_user.wasm" - -# Default validator VP -[wasm.vp_validator] -# filename (relative to wasm path used by the node) -filename = "vp_validator.wasm" - -# MASP VP -[wasm.vp_masp] -filename = "vp_masp.wasm" - -# General protocol parameters. -[parameters] -# Minimum number of blocks in an epoch. -min_num_of_blocks = 4 -# Maximum expected time per block (in seconds). -max_expected_time_per_block = 30 -# Max payload size, in bytes, for a tx batch proposal. -max_proposal_bytes = 22020096 -# Max amount of gas per block -max_block_gas = 20000000 -# Fee unshielding gas limit -fee_unshielding_gas_limit = 20000 -# Fee unshielding descriptions limit -fee_unshielding_descriptions_limit = 15 -# vp whitelist -vp_whitelist = [] -# tx whitelist -tx_whitelist = [] -# Implicit VP WASM name -implicit_vp = "vp_implicit" -# Expected number of epochs per year (also sets the min duration of an epoch in seconds) -epochs_per_year = 105_120 # 5 minute epochs -# The P gain factor in the Proof of Stake rewards controller -pos_gain_p = "0.1" -# The D gain factor in the Proof of Stake rewards controller -pos_gain_d = "0.1" -# The maximum number of signatures allowed per transaction -max_signatures_per_transaction = 15 - -[parameters.minimum_gas_price] -"atest1v4ehgw36x3prswzxggunzv6pxqmnvdj9xvcyzvpsggeyvs3cg9qnywf589qnwvfsg5erg3fkl09rg5" = "0.000001" - -# Proof of stake parameters. -[pos_params] -# Maximum number of consensus validators. -max_validator_slots = 128 -# Pipeline length (in epochs). Any change in the validator set made in -# epoch 'n' will become active in epoch 'n + pipeline_len'. -pipeline_len = 2 -# Unbonding length (in epochs). Validators may have their stake slashed -# for a fault in epoch 'n' up through epoch 'n + unbonding_len'. -unbonding_len = 3 -# Votes per fundamental staking token (namnam) -tm_votes_per_token = "0.1" -# Reward for proposing a block. -block_proposer_reward = "0.125" -# Reward for voting on a block. -block_vote_reward = "0.1" -# Maximum inflation rate per annum (10%) -max_inflation_rate = "0.1" -# Targeted ratio of staked tokens to total tokens in the supply -target_staked_ratio = "0.6667" -# Portion of a validator's stake that should be slashed on a duplicate -# vote. -duplicate_vote_min_slash_rate = "0.001" -# Portion of a validator's stake that should be slashed on a light -# client attack. -light_client_attack_min_slash_rate = "0.001" -# Number of epochs above and below (separately) the current epoch to -# consider when doing cubic slashing -cubic_slashing_window_length = 1 -# The minimum amount of bonded tokens that a validator needs to be in -# either the `consensus` or `below_capacity` validator sets -validator_stake_threshold = "1" - -# Governance parameters. -[gov_params] -# minimum amount of nam token to lock -min_proposal_fund = 500 -# proposal code size in bytes -max_proposal_code_size = 500000 -# min proposal voting period length in epochs -min_proposal_voting_period = 3 -# max proposal period length in epochs -max_proposal_period = 27 -# maximum number of characters in the proposal content -max_proposal_content_size = 10000 -# minimum epochs between end and grace epoch -min_proposal_grace_epochs = 6 - -[pgf_params] -# list of steward address at genezis -stewards = [] -# inflation rate for pgf fundings -pgf_inflation_rate = "0.1" -# inflation rate for pgf stewards -stewards_inflation_rate = "0.01" \ No newline at end of file diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml deleted file mode 100644 index 1096be0d28..0000000000 --- a/genesis/e2e-tests-single-node.toml +++ /dev/null @@ -1,260 +0,0 @@ -# Genesis configuration source for E2E tests with: -# - 1 genesis validator -# - User accounts same as the ones in "dev" build (Albert, Bertha, Christel) - -genesis_time = "2021-09-30T10:00:00Z" -native_token = "NAM" - -[validator.validator-0] -# Validator's staked NAM at genesis. -tokens = 200000 -# Amount of the validator's genesis token balance which is not staked. -non_staked_balance = 1000000000000 -# VP for the validator account -validator_vp = "vp_validator" -# Commission rate for rewards -commission_rate = "0.05" -# Maximum change per epoch in the commission rate -max_commission_rate_change = "0.01" -# (Public IP | Hostname):port address. -# We set the port to be the default+1000, so that if a local node was running at -# the same time as the E2E tests, it wouldn't affect them. -net_address = "127.0.0.1:27656" - -# Some tokens present at genesis. - -[token.NAM] -address = "atest1v4ehgw36x3prswzxggunzv6pxqmnvdj9xvcyzvpsggeyvs3cg9qnywf589qnwvfsg5erg3fkl09rg5" -denom = 6 -[token.NAM.balances] -Albert = "1000000" -"Albert.public_key" = "100" -Bertha = "1000000" -"Bertha.public_key" = "2000" -Christel = "1000000" -"Christel.public_key" = "100" -Daewon = "1000000" -Ester = "1000000" -"validator-0.public_key" = "100" -[token.NAM.parameters] -max_reward_rate = "0.1" -kd_gain_nom = "0.1" -kp_gain_nom = "0.1" -locked_ratio_target = "0.6667" - -[token.BTC] -address = "atest1v4ehgw36xdzryve5gsc52veeg5cnsv2yx5eygvp38qcrvd29xy6rys6p8yc5xvp4xfpy2v694wgwcp" -denom = 8 -[token.BTC.balances] -Albert = "1000000" -Bertha = "1000000" -Christel = "1000000" -Daewon = "1000000" -Ester = "1000000" -[token.BTC.parameters] -max_reward_rate = "0.1" -kd_gain_nom = "0.1" -kp_gain_nom = "0.1" -locked_ratio_target = "0.6667" - -[token.ETH] -address = "atest1v4ehgw36xqmr2d3nx3ryvd2xxgmrq33j8qcns33sxezrgv6zxdzrydjrxveygd2yxumrsdpsf9jc2p" -denom = 18 -[token.ETH.balances] -Albert = "1000000" -Bertha = "1000000" -Christel = "1000000" -Daewon = "1000000" -Ester = "1000000" -[token.ETH.parameters] -max_reward_rate = "0.1" -kd_gain_nom = "0.1" -kp_gain_nom = "0.1" -locked_ratio_target = "0.6667" - -[token.DOT] -address = "atest1v4ehgw36gg6nvs2zgfpyxsfjgc65yv6pxy6nwwfsxgungdzrggeyzv35gveyxsjyxymyz335hur2jn" -denom = 10 -[token.DOT.balances] -Albert = "1000000" -Bertha = "1000000" -Christel = "1000000" -Daewon = "1000000" -Ester = "1000000" -[token.DOT.parameters] -max_reward_rate = "0.1" -kd_gain_nom = "0.1" -kp_gain_nom = "0.1" -locked_ratio_target = "0.6667" - -[token.Schnitzel] -address = "atest1v4ehgw36xue5xvf5xvuyzvpjx5un2v3k8qeyvd3cxdqns32p89rrxd6xx9zngvpegccnzs699rdnnt" -denom = 6 -[token.Schnitzel.balances] -Albert = "1000000" -Bertha = "1000000" -Christel = "1000000" -Daewon = "1000000" -Ester = "1000000" -[token.Schnitzel.parameters] -max_reward_rate = "0.1" -kd_gain_nom = "0.1" -kp_gain_nom = "0.1" -locked_ratio_target = "0.6667" - -[token.Apfel] -address = "atest1v4ehgw36gfryydj9g3p5zv3kg9znyd358ycnzsfcggc5gvecgc6ygs2rxv6ry3zpg4zrwdfeumqcz9" -denom = 6 -[token.Apfel.balances] -Albert = "1000000" -Bertha = "1000000" -Christel = "1000000" -Daewon = "1000000" -Ester = "1000000" -[token.Apfel.parameters] -max_reward_rate = "0.1" -kd_gain_nom = "0.1" -kp_gain_nom = "0.1" -locked_ratio_target = "0.6667" - -[token.Kartoffel] -address = "atest1v4ehgw36gep5ysecxq6nyv3jg3zygv3e89qn2vp48pryxsf4xpznvve5gvmy23fs89pryvf5a6ht90" -public_key = "" -denom = 6 -[token.Kartoffel.balances] -Albert = "1000000" -Bertha = "1000000" -Christel = "1000000" -Daewon = "1000000" -Ester = "1000000" -[token.Kartoffel.parameters] -max_reward_rate = "0.1" -kd_gain_nom = "0.1" -kp_gain_nom = "0.1" -locked_ratio_target = "0.6667" - -[established.Albert] -vp = "vp_user" - -[established.Bertha] -vp = "vp_user" - -[established.Christel] -vp = "vp_user" - -[established.masp] -address = "atest1v4ehgw36xaryysfsx5unvve4g5my2vjz89p52sjxxgenzd348yuyyv3hg3pnjs35g5unvde4ca36y5" -vp = "vp_masp" - -[implicit.Daewon] - -[implicit.Ester] - -# Wasm VP definitions - -# Implicit VP -[wasm.vp_implicit] -filename = "vp_implicit.wasm" - -# Default user VP in established accounts -[wasm.vp_user] -filename = "vp_user.wasm" - -# Default validator VP -[wasm.vp_validator] -# filename (relative to wasm path used by the node) -filename = "vp_validator.wasm" - -# MASP VP -[wasm.vp_masp] -filename = "vp_masp.wasm" - -# General protocol parameters. -[parameters] -# Minimum number of blocks in an epoch. -min_num_of_blocks = 4 -# Maximum expected time per block (in seconds). -max_expected_time_per_block = 30 -# Max payload size, in bytes, for a tx batch proposal. -max_proposal_bytes = 22020096 -# Max amount of gas per block -max_block_gas = 20000000 -# Fee unshielding gas limit -fee_unshielding_gas_limit = 20000 -# Fee unshielding descriptions limit -fee_unshielding_descriptions_limit = 15 -# vp whitelist -vp_whitelist = [] -# tx whitelist -tx_whitelist = [] -# Implicit VP WASM name -implicit_vp = "vp_implicit" -# Expected number of epochs per year (also sets the min duration of an epoch in seconds). -# Remember to set this to a more reasonable number for production networks. Also, expect most masp -# txs to fail due to epoch boundary errors. -epochs_per_year = 31_536_000 -# The P gain factor in the Proof of Stake rewards controller -pos_gain_p = "0.1" -# The D gain factor in the Proof of Stake rewards controller -pos_gain_d = "0.1" -# The maximum number of signatures allowed per transaction -max_signatures_per_transaction = 15 - -[parameters.minimum_gas_price] -"atest1v4ehgw36x3prswzxggunzv6pxqmnvdj9xvcyzvpsggeyvs3cg9qnywf589qnwvfsg5erg3fkl09rg5" = "0.000001" - -# Proof of stake parameters. -[pos_params] -# Maximum number of consensus validators. -max_validator_slots = 128 -# Pipeline length (in epochs). Any change in the validator set made in -# epoch 'n' will become active in epoch 'n + pipeline_len'. -pipeline_len = 2 -# Unbonding length (in epochs). Validators may have their stake slashed -# for a fault in epoch 'n' up through epoch 'n + unbonding_len'. -unbonding_len = 3 -# Votes per fundamental staking token (namnam) - for testnets this should be 1. For e2e toml, a decimal is used for testing purposes. -tm_votes_per_token = "0.1" -# Reward for proposing a block. -block_proposer_reward = "0.125" -# Reward for voting on a block. -block_vote_reward = "0.1" -# Maximum inflation rate per annum (10%) -max_inflation_rate = "0.1" -# Targeted ratio of staked tokens to total tokens in the supply -target_staked_ratio = "0.6667" -# Portion of a validator's stake that should be slashed on a duplicate -# vote. -duplicate_vote_min_slash_rate = "0.001" -# Portion of a validator's stake that should be slashed on a light -# client attack. -light_client_attack_min_slash_rate = "0.001" -# Number of epochs above and below (separately) the current epoch to -# consider when doing cubic slashing -cubic_slashing_window_length = 1 -# The minimum amount of bonded tokens that a validator needs to be in -# either the `consensus` or `below_capacity` validator sets -validator_stake_threshold = "1" - -# Governance parameters. -[gov_params] -# minimum amount of nam token to lock -min_proposal_fund = 500 -# proposal code size in bytes -max_proposal_code_size = 1000000 -# min proposal period length in epochs -min_proposal_voting_period = 3 -# max proposal period length in epochs -max_proposal_period = 27 -# maximum number of characters in the proposal content -max_proposal_content_size = 10000 -# minimum epochs between end and grace epoch -min_proposal_grace_epochs = 6 - -[pgf_params] -# list of steward address at genezis -stewards = [] -# inflation rate for pgf fundings -pgf_inflation_rate = "0.1" -# inflation rate for pgf stewards -stewards_inflation_rate = "0.01" \ No newline at end of file diff --git a/genesis/localnet/README.md b/genesis/localnet/README.md new file mode 100644 index 0000000000..e483f7aa07 --- /dev/null +++ b/genesis/localnet/README.md @@ -0,0 +1,66 @@ +# Localnet genesis templates + +This directory contains genesis templates for a local network with a single validator. The `src` directory contains generated pre-genesis wallet pre-loaded with unencrypted keys and a single validator `"validator-0" wallet that are being used in the templates. + +If you're modifying any of the files here, you can run this to ensure that the changes are valid: + +```shell +cargo watch -x "test test_validate_localnet_genesis_templates" +``` + +## balances.toml + +The pre-genesis balances wallet is located at [pre-genesis/wallet.toml](pre-genesis/wallet.toml) was used to setup the [balances.toml](balances.toml) and can be re-generated from the repo's root dir with: + +```shell +cargo run --bin namadaw -- --base-dir "genesis/localnet/src" key gen \ + --alias albert-key --unsafe-dont-encrypt +cargo run --bin namadaw -- --base-dir "genesis/localnet/src" key gen \ + --alias bertha-key --unsafe-dont-encrypt +cargo run --bin namadaw -- --base-dir "genesis/localnet/src" key gen \ + --alias christel --unsafe-dont-encrypt +cargo run --bin namadaw -- --base-dir "genesis/localnet/src" key gen \ + --alias daewon --unsafe-dont-encrypt +cargo run --bin namadaw -- --base-dir "genesis/localnet/src" key gen \ + --alias validator-0-balance-key --unsafe-dont-encrypt +cargo run --bin namadaw -- --base-dir "genesis/localnet/src" key gen \ + --alias faucet-key --unsafe-dont-encrypt +``` + +The [balances.toml file](balances.toml) contains token balances associated with the public keys. The public keys from the wallet can be found with: + +```shell +cargo run --bin namadaw -- --base-dir "genesis/localnet/src" key list +``` + +## transactions.toml + +The pre-genesis validator wallet used to generate [validator transactions for transactions.toml](src/pre-genesis/validator-0/transactions.toml) is located at [src/pre-genesis/validator-0/validator-wallet.toml](src/pre-genesis/validator-0/validator-wallet.toml) and can be re-generated from the repo's root dir with: + +```shell +cargo run --bin namadac -- --base-dir "genesis/localnet/src" utils \ + init-genesis-validator \ + --source validator-0-balance-key \ + --alias validator-0 \ + --net-address "127.0.0.1:27656" \ + --commission-rate 0.05 \ + --max-commission-rate-change 0.01 \ + --transfer-from-source-amount 200000 \ + --self-bond-amount 100000 \ + --unsafe-dont-encrypt +``` + +The rest of the transactions are generated from [src/pre-genesis/unsigned-transactions.toml](src/pre-genesis/unsigned-transactions.toml) using: + +```shell +cargo run --bin namadac -- --base-dir "genesis/localnet/src" utils \ + sign-genesis-tx \ + --path "genesis/localnet/src/pre-genesis/unsigned-transactions.toml" \ + --output "genesis/localnet/src/pre-genesis/signed-transactions.toml" +``` + +This command produces [src/pre-genesis/signed-transactions.toml](src/pre-genesis/signed-transactions.toml), which is then concatenated in [transactions.toml](transactiosn.toml) with the validator transactions. + +## Validation + +A unit test `test_localnet_genesis_templates` is setup to check validity of the localnet setup. diff --git a/genesis/localnet/balances.toml b/genesis/localnet/balances.toml new file mode 100644 index 0000000000..2c2d78a9f3 --- /dev/null +++ b/genesis/localnet/balances.toml @@ -0,0 +1,110 @@ +# Genesis balances. +# +# This files contains the genesis balances of any tokens present at genesis +# associated with public keys. +# +# For example: +# ``` +# [token.NAM] +# some_pk_bech32m_encoding = 10 # genesis tokens, the amount can have up to 6 decimal places +# ``` +# +# The public keys present in here are taken from `pre-genesis/wallet.toml` + +[token.NAM] +# albert-key +pktest1qz0aphcsrw37j8fy742cjwhphu9jwx7esd3ad4xxtxrkwv07ff63wnnul44 = "2000000" +# bertha-key +pktest1qpyfnrl6qdqvguah9kknvp9t6ajcrec7fge56pcgaa655zkua3ndsl9tn4a = "2000000" +# christel +pktest1qp6uy52q0fldjxupznuskm69fkuswx3fq3vw9kekzp4enkh5h7pmzy242c2 = "2000000" +# daewon +pktest1qzz4x4fammhdcfa0g8xw4udkq8s4n6kjhzlxh00ul3da05wuu9wkyuzdckd = "100000000" +# ester +pktest1qypvqpzu74nafjahlwyq272dj76qq9rz30dulyc94883tmj893mquqsafsprd = "1000000" +# validator-0-key +pktest1qzp22w6trhmxmp2dx5h883c2l684z0e20a9egusmxaat62wvaa4agcthpup = "200000" + +[token.BTC] +# albert-key +pktest1qz0aphcsrw37j8fy742cjwhphu9jwx7esd3ad4xxtxrkwv07ff63wnnul44 = "1000000" +# bertha-key +pktest1qpyfnrl6qdqvguah9kknvp9t6ajcrec7fge56pcgaa655zkua3ndsl9tn4a = "1000000" +# christel +pktest1qp6uy52q0fldjxupznuskm69fkuswx3fq3vw9kekzp4enkh5h7pmzy242c2 = "1000000" +# daewon +pktest1qzz4x4fammhdcfa0g8xw4udkq8s4n6kjhzlxh00ul3da05wuu9wkyuzdckd = "1000000" +# ester +pktest1qypvqpzu74nafjahlwyq272dj76qq9rz30dulyc94883tmj893mquqsafsprd = "1000000" +# validator-0-key +pktest1qzp22w6trhmxmp2dx5h883c2l684z0e20a9egusmxaat62wvaa4agcthpup = "1000000" + +[token.ETH] +# albert-key +pktest1qz0aphcsrw37j8fy742cjwhphu9jwx7esd3ad4xxtxrkwv07ff63wnnul44 = "1000000" +# bertha-key +pktest1qpyfnrl6qdqvguah9kknvp9t6ajcrec7fge56pcgaa655zkua3ndsl9tn4a = "1000000" +# christel +pktest1qp6uy52q0fldjxupznuskm69fkuswx3fq3vw9kekzp4enkh5h7pmzy242c2 = "1000000" +# daewon +pktest1qzz4x4fammhdcfa0g8xw4udkq8s4n6kjhzlxh00ul3da05wuu9wkyuzdckd = "1000000" +# ester +pktest1qypvqpzu74nafjahlwyq272dj76qq9rz30dulyc94883tmj893mquqsafsprd = "1000000" +# validator-0-key +pktest1qzp22w6trhmxmp2dx5h883c2l684z0e20a9egusmxaat62wvaa4agcthpup = "1000000" + +[token.DOT] +# albert-key +pktest1qz0aphcsrw37j8fy742cjwhphu9jwx7esd3ad4xxtxrkwv07ff63wnnul44 = "1000000" +# bertha-key +pktest1qpyfnrl6qdqvguah9kknvp9t6ajcrec7fge56pcgaa655zkua3ndsl9tn4a = "1000000" +# christel +pktest1qp6uy52q0fldjxupznuskm69fkuswx3fq3vw9kekzp4enkh5h7pmzy242c2 = "1000000" +# daewon +pktest1qzz4x4fammhdcfa0g8xw4udkq8s4n6kjhzlxh00ul3da05wuu9wkyuzdckd = "1000000" +# ester +pktest1qypvqpzu74nafjahlwyq272dj76qq9rz30dulyc94883tmj893mquqsafsprd = "1000000" +# validator-0-key +pktest1qzp22w6trhmxmp2dx5h883c2l684z0e20a9egusmxaat62wvaa4agcthpup = "1000000" + +[token.Schnitzel] +# albert-key +pktest1qz0aphcsrw37j8fy742cjwhphu9jwx7esd3ad4xxtxrkwv07ff63wnnul44 = "1000000" +# bertha-key +pktest1qpyfnrl6qdqvguah9kknvp9t6ajcrec7fge56pcgaa655zkua3ndsl9tn4a = "1000000" +# christel +pktest1qp6uy52q0fldjxupznuskm69fkuswx3fq3vw9kekzp4enkh5h7pmzy242c2 = "1000000" +# daewon +pktest1qzz4x4fammhdcfa0g8xw4udkq8s4n6kjhzlxh00ul3da05wuu9wkyuzdckd = "1000000" +# ester +pktest1qypvqpzu74nafjahlwyq272dj76qq9rz30dulyc94883tmj893mquqsafsprd = "1000000" +# validator-0-key +pktest1qzp22w6trhmxmp2dx5h883c2l684z0e20a9egusmxaat62wvaa4agcthpup = "1000000" + +[token.Apfel] +# albert-key +pktest1qz0aphcsrw37j8fy742cjwhphu9jwx7esd3ad4xxtxrkwv07ff63wnnul44 = "1000000" +# bertha-key +pktest1qpyfnrl6qdqvguah9kknvp9t6ajcrec7fge56pcgaa655zkua3ndsl9tn4a = "1000000" +# christel +pktest1qp6uy52q0fldjxupznuskm69fkuswx3fq3vw9kekzp4enkh5h7pmzy242c2 = "1000000" +# daewon +pktest1qzz4x4fammhdcfa0g8xw4udkq8s4n6kjhzlxh00ul3da05wuu9wkyuzdckd = "1000000" +# ester +pktest1qypvqpzu74nafjahlwyq272dj76qq9rz30dulyc94883tmj893mquqsafsprd = "1000000" +# validator-0-key +pktest1qzp22w6trhmxmp2dx5h883c2l684z0e20a9egusmxaat62wvaa4agcthpup = "1000000" + +[token.Kartoffel] +# albert-key +pktest1qz0aphcsrw37j8fy742cjwhphu9jwx7esd3ad4xxtxrkwv07ff63wnnul44 = "1000000" +# bertha-key +pktest1qpyfnrl6qdqvguah9kknvp9t6ajcrec7fge56pcgaa655zkua3ndsl9tn4a = "1000000" +# christel +pktest1qp6uy52q0fldjxupznuskm69fkuswx3fq3vw9kekzp4enkh5h7pmzy242c2 = "1000000" +# daewon +pktest1qzz4x4fammhdcfa0g8xw4udkq8s4n6kjhzlxh00ul3da05wuu9wkyuzdckd = "1000000" +# ester +pktest1qypvqpzu74nafjahlwyq272dj76qq9rz30dulyc94883tmj893mquqsafsprd = "1000000" +# validator-0-key +pktest1qzp22w6trhmxmp2dx5h883c2l684z0e20a9egusmxaat62wvaa4agcthpup = "1000000" \ No newline at end of file diff --git a/genesis/localnet/parameters.toml b/genesis/localnet/parameters.toml new file mode 100644 index 0000000000..41fb316f34 --- /dev/null +++ b/genesis/localnet/parameters.toml @@ -0,0 +1,90 @@ +# General protocol parameters. +[parameters] +native_token = "NAM" +# Minimum number of blocks in an epoch. +min_num_of_blocks = 4 +# Maximum expected time per block (in seconds). +max_expected_time_per_block = 30 +# Max payload size, in bytes, for a tx batch proposal. +max_proposal_bytes = 22020096 +# vp whitelist +vp_whitelist = [] +# tx whitelist +tx_whitelist = [] +# Implicit VP WASM name +implicit_vp = "vp_implicit" +# Expected number of epochs per year (also sets the min duration of an epoch in seconds) +epochs_per_year = 31_536_000 +# The P gain factor in the Proof of Stake rewards controller +pos_gain_p = "0.1" +# The D gain factor in the Proof of Stake rewards controller +pos_gain_d = "0.1" +# Maximum number of signature per transaction +max_signatures_per_transaction = 15 +# Max gas for block +max_block_gas = 20000000 +# Fee unshielding gas limit +fee_unshielding_gas_limit = 20000 +# Fee unshielding descriptions limit +fee_unshielding_descriptions_limit = 15 + +# Map of the cost per gas unit for every token allowed for fee payment +[parameters.minimum_gas_price] +nam = "0.000001" + +# Proof of stake parameters. +[pos_params] +# Maximum number of active validators. +max_validator_slots = 128 +# Pipeline length (in epochs). Any change in the validator set made in +# epoch 'n' will become active in epoch 'n + pipeline_len'. +pipeline_len = 2 +# Unbonding length (in epochs). Validators may have their stake slashed +# for a fault in epoch 'n' up through epoch 'n + unbonding_len'. +unbonding_len = 3 +# Votes per fundamental staking token (namnam) +tm_votes_per_token = "1" +# Reward for proposing a block. +block_proposer_reward = "0.125" +# Reward for voting on a block. +block_vote_reward = "0.1" +# Maximum inflation rate per annum (10%) +max_inflation_rate = "0.1" +# Targeted ratio of staked tokens to total tokens in the supply +target_staked_ratio = "0.6667" +# Portion of a validator's stake that should be slashed on a duplicate +# vote. +duplicate_vote_min_slash_rate = "0.001" +# Portion of a validator's stake that should be slashed on a light +# client attack. +light_client_attack_min_slash_rate = "0.001" +# Number of epochs above and below (separately) the current epoch to +# consider when doing cubic slashing +cubic_slashing_window_length = 1 +# The minimum amount of bonded tokens that a validator needs to be in +# either the `consensus` or `below_capacity` validator sets +validator_stake_threshold = "1" + +# Governance parameters. +[gov_params] +# minimum amount of nam token to lock +min_proposal_fund = 500 +# proposal code size in bytes +max_proposal_code_size = 600000 +# min proposal period length in epochs +min_proposal_voting_period = 3 +# max proposal period length in epochs +max_proposal_period = 27 +# maximum number of characters in the proposal content +max_proposal_content_size = 10000 +# minimum epochs between end and grace epoch +min_proposal_grace_epochs = 6 + +# Public goods funding parameters +[pgf_params] +# Initial set of stewards +stewards = ["bertha", "validator-0"] +# The pgf funding inflation rate +pgf_inflation_rate = "0.1" +# The pgf stewards inflation rate +stewards_inflation_rate = "0.01" diff --git a/genesis/localnet/src/pre-genesis/signed-transactions.toml b/genesis/localnet/src/pre-genesis/signed-transactions.toml new file mode 100644 index 0000000000..722e4cc430 --- /dev/null +++ b/genesis/localnet/src/pre-genesis/signed-transactions.toml @@ -0,0 +1,126 @@ +[[established_account]] +alias = "albert" +vp = "vp_user" + +[established_account.public_key] +pk = "pktest1qz0aphcsrw37j8fy742cjwhphu9jwx7esd3ad4xxtxrkwv07ff63wnnul44" +authorization = "sigtest1qzq6dwf2a9dq4hp3nmxfgckh55mulzryxsfkqhr9uvfvmtr9wt38lyyvzvfqryxnat2a4ry6hygv957z683dyngu03gse2uvl5ldfccypktkp6" + +[established_account.storage] + +[[established_account]] +alias = "bertha" +vp = "vp_user" + +[established_account.public_key] +pk = "pktest1qpyfnrl6qdqvguah9kknvp9t6ajcrec7fge56pcgaa655zkua3ndsl9tn4a" +authorization = "sigtest1qzt58nd7k2mj647x8x4ydhsjkut7dmsl7yjlnrnwu9kzjdch3cljv6dq05mx2kvwn80kjezh7lz26adc5ksvyn3knufymtkhlmnhg3c8zka3y4" + +[established_account.storage] + +[[established_account]] +alias = "christel" +vp = "vp_user" + +[established_account.public_key] +pk = "pktest1qp6uy52q0fldjxupznuskm69fkuswx3fq3vw9kekzp4enkh5h7pmzy242c2" +authorization = "sigtest1qrf2l6a5u4ywskryhdueudnm50j3kqhujas3mmktenqm89fmlskjnyeskr2tr7js5swmtqqtenkj6ap9xpelx2w40fjjczc4w9xtdggqjtszy4" + +[established_account.storage] + +[[transfer]] +token = "nam" +source = "pktest1qz0aphcsrw37j8fy742cjwhphu9jwx7esd3ad4xxtxrkwv07ff63wnnul44" +target = "albert" +amount = "1000000" +signature = "sigtest1qrxfthuz5yzapdt82d8nc656ffrzkxnkfg6fw8w7e6045vcpn8kpr4c536ug4fs2ddz5823dfd3w4qnhcl40qmtasfccaa99a0hvjcqvyy2l02" + +[[transfer]] +token = "btc" +source = "pktest1qz0aphcsrw37j8fy742cjwhphu9jwx7esd3ad4xxtxrkwv07ff63wnnul44" +target = "albert" +amount = "1000000" +signature = "sigtest1qq826qel8zn5p4dfx25ma45uxeskx0yjlsaxp38uyvehvds6t93c8wkhaydunm4nzz9750dwhkjst00mfrwmu066y3rj6gtk5wjskxq9zvk7ed" + +[[transfer]] +token = "eth" +source = "pktest1qz0aphcsrw37j8fy742cjwhphu9jwx7esd3ad4xxtxrkwv07ff63wnnul44" +target = "albert" +amount = "1000000" +signature = "sigtest1qzghddk0u6kpqaxhskrj8ah69d3hce95wndgvlpuyvuw0h0fnyrnp026ltvtfmfhpzywkf88yyhv80hzkus57s9nn6vn3ljvmkkf7zgq2nzn40" + +[[transfer]] +token = "dot" +source = "pktest1qz0aphcsrw37j8fy742cjwhphu9jwx7esd3ad4xxtxrkwv07ff63wnnul44" +target = "albert" +amount = "1000000" +signature = "sigtest1qqlu8kajh0up5zhewd2h44fhq7zdaapqt9xaj8xp7jmdqpv4dl9d5tesw3wyr9su962rvpkf02x27579vqjk4f4advx7t9ejz74cjrc25puna6" + +[[transfer]] +token = "schnitzel" +source = "pktest1qz0aphcsrw37j8fy742cjwhphu9jwx7esd3ad4xxtxrkwv07ff63wnnul44" +target = "albert" +amount = "1000000" +signature = "sigtest1qpukdd6f7pvtgau9v58xxwrnmv5pjra6527aggce9j8cu6fnekzag2cmmf4w0x0cn76gpth2vw6zcsnw37xjuz96hflqfnk5sznxe6cfd5mjnv" + +[[transfer]] +token = "apfel" +source = "pktest1qz0aphcsrw37j8fy742cjwhphu9jwx7esd3ad4xxtxrkwv07ff63wnnul44" +target = "albert" +amount = "1000000" +signature = "sigtest1qprnsyhlruvw4a6jjs98xdd04gxdsgycywzt79gyyak68gn4f50ppdvdm0q6ns44cheu7s43kal0qsldml8s0zexqk3s9zmglu4l6fszwcqswy" + +[[transfer]] +token = "kartoffel" +source = "pktest1qz0aphcsrw37j8fy742cjwhphu9jwx7esd3ad4xxtxrkwv07ff63wnnul44" +target = "albert" +amount = "1000000" +signature = "sigtest1qpg4y6ltax3qfclguym2kpa0ezlkf4hz4u3h8fc2ndmuzm4zfe26ygq9u6nhjjvj93n5v296x4arkvjk22ygee2fx054ce7ap6gvynq2j2k0vf" + +[[transfer]] +token = "nam" +source = "pktest1qpyfnrl6qdqvguah9kknvp9t6ajcrec7fge56pcgaa655zkua3ndsl9tn4a" +target = "bertha" +amount = "1000000" +signature = "sigtest1qpe3ms76cxvqsa58624ked5ndjr3jr3u7lf9ypalxdh29gajwmsxtzjxzswk0xmza90u47tgksr0d7enpd730ps7fq5u76l7ekxeyvgfnl0qxj" + +[[transfer]] +token = "btc" +source = "pktest1qpyfnrl6qdqvguah9kknvp9t6ajcrec7fge56pcgaa655zkua3ndsl9tn4a" +target = "bertha" +amount = "1000000" +signature = "sigtest1qqq4yc6s3h4ysezcmgsdgyfsqu8mmrwwc8a358sm864zehx74ums8fmgmkyyr3wz9qqrc5fagwrw0a4mgqkkleumc0u6ynshj5ns0tcdrygupg" + +[[transfer]] +token = "eth" +source = "pktest1qpyfnrl6qdqvguah9kknvp9t6ajcrec7fge56pcgaa655zkua3ndsl9tn4a" +target = "bertha" +amount = "1000000" +signature = "sigtest1qzyqkdxt3cxf0huv0hzskqmuthw38s3kj2sx50znzc68gzx64hrd3hkyx0g27pruwa9fv42suw3k8gkfaelcp4ymnvfw7ltx0mz2z9qr2lwwcs" + +[[transfer]] +token = "nam" +source = "pktest1qp6uy52q0fldjxupznuskm69fkuswx3fq3vw9kekzp4enkh5h7pmzy242c2" +target = "christel" +amount = "1000000" +signature = "sigtest1qran2jh0dwuwchh45snhk3p9uyxqym57eg527pcjeekwhvpr5j0fy6dte7x3v2dwwc6vqmv49drpvmk6uhzlt2p8rqx0th3h7smf8rqrcpfxc7" + +[[transfer]] +token = "btc" +source = "pktest1qp6uy52q0fldjxupznuskm69fkuswx3fq3vw9kekzp4enkh5h7pmzy242c2" +target = "christel" +amount = "1000000" +signature = "sigtest1qrxp6p56qtvsyp7jfs4jru3mkfv6l4t7hpguur72579pqjaexdry9turgmzwzj6qmua3s2y2a4krtuxwaag66j4dpz44aaqksy30tfcxmnhw2k" + +[[transfer]] +token = "eth" +source = "pktest1qp6uy52q0fldjxupznuskm69fkuswx3fq3vw9kekzp4enkh5h7pmzy242c2" +target = "christel" +amount = "1000000" +signature = "sigtest1qrtrec6mps9p7zhmm7sywxwc7zdm32umt0zdwpuflrkh6952rx74amnsf9hn0yughs4e6czgpdmwsmujvkr4ptk2x24vtc8phz0s2asvtyy9pf" + +[[bond]] +source = "albert" +validator = "validator-0" +amount = "20000" +signature = "sigtest1qpx0kc8rl966vnnswfwhcwxh5vv69p7lev2apc2yephag978gqg5903kuw6dugg9c6pg5jdw048umk72ntympphz0dka74aspldslaqr3q7eqr" diff --git a/genesis/localnet/src/pre-genesis/unsigned-transactions.toml b/genesis/localnet/src/pre-genesis/unsigned-transactions.toml new file mode 100644 index 0000000000..00c82cf511 --- /dev/null +++ b/genesis/localnet/src/pre-genesis/unsigned-transactions.toml @@ -0,0 +1,114 @@ +# This file contains hand-written unsigned transactions for localnet with: +# +# - MASP account +# - Faucet account that allows anyone to withdraw limited amount of tokens +# - 2 established accounts for "Albert" and "Bertha" +# +# Note that 2 more localnet user accounts "Christel" and "Daewon" are left as +# implicit accounts, so their tokens are kept in the accounts derived from their +# keys used in `balances.toml`. +# +# This file is used to produce `signed-transactions.toml` with +# the `sign-genesis-tx` command. + +# Albert +[[established_account]] +alias = "albert" +vp = "vp_user" +public_key = "pktest1qz0aphcsrw37j8fy742cjwhphu9jwx7esd3ad4xxtxrkwv07ff63wnnul44" + +# Bertha +[[established_account]] +alias = "bertha" +vp = "vp_user" +public_key = "pktest1qpyfnrl6qdqvguah9kknvp9t6ajcrec7fge56pcgaa655zkua3ndsl9tn4a" + +# Christel +[[established_account]] +alias = "christel" +vp = "vp_user" +public_key = "pktest1qp6uy52q0fldjxupznuskm69fkuswx3fq3vw9kekzp4enkh5h7pmzy242c2" + +# Transfer all the Albert's tokens into it's established account: +[[transfer]] +token = "NAM" +source = "pktest1qz0aphcsrw37j8fy742cjwhphu9jwx7esd3ad4xxtxrkwv07ff63wnnul44" +target = "albert" +amount = "1000000" + +[[transfer]] +token = "BTC" +source = "pktest1qz0aphcsrw37j8fy742cjwhphu9jwx7esd3ad4xxtxrkwv07ff63wnnul44" +target = "albert" +amount = "1000000" + +[[transfer]] +token = "ETH" +source = "pktest1qz0aphcsrw37j8fy742cjwhphu9jwx7esd3ad4xxtxrkwv07ff63wnnul44" +target = "albert" +amount = "1000000" + +[[transfer]] +token = "DOT" +source = "pktest1qz0aphcsrw37j8fy742cjwhphu9jwx7esd3ad4xxtxrkwv07ff63wnnul44" +target = "albert" +amount = "1000000" + +[[transfer]] +token = "Schnitzel" +source = "pktest1qz0aphcsrw37j8fy742cjwhphu9jwx7esd3ad4xxtxrkwv07ff63wnnul44" +target = "albert" +amount = "1000000" + +[[transfer]] +token = "Apfel" +source = "pktest1qz0aphcsrw37j8fy742cjwhphu9jwx7esd3ad4xxtxrkwv07ff63wnnul44" +target = "albert" +amount = "1000000" + +[[transfer]] +token = "Kartoffel" +source = "pktest1qz0aphcsrw37j8fy742cjwhphu9jwx7esd3ad4xxtxrkwv07ff63wnnul44" +target = "albert" +amount = "1000000" + +[[transfer]] +token = "NAM" +source = "pktest1qpyfnrl6qdqvguah9kknvp9t6ajcrec7fge56pcgaa655zkua3ndsl9tn4a" +target = "bertha" +amount = "1000000" + +[[transfer]] +token = "BTC" +source = "pktest1qpyfnrl6qdqvguah9kknvp9t6ajcrec7fge56pcgaa655zkua3ndsl9tn4a" +target = "bertha" +amount = "1000000" + +[[transfer]] +token = "ETH" +source = "pktest1qpyfnrl6qdqvguah9kknvp9t6ajcrec7fge56pcgaa655zkua3ndsl9tn4a" +target = "bertha" +amount = "1000000" + +[[transfer]] +token = "NAM" +source = "pktest1qp6uy52q0fldjxupznuskm69fkuswx3fq3vw9kekzp4enkh5h7pmzy242c2" +target = "christel" +amount = "1000000" + +[[transfer]] +token = "BTC" +source = "pktest1qp6uy52q0fldjxupznuskm69fkuswx3fq3vw9kekzp4enkh5h7pmzy242c2" +target = "christel" +amount = "1000000" + +[[transfer]] +token = "ETH" +source = "pktest1qp6uy52q0fldjxupznuskm69fkuswx3fq3vw9kekzp4enkh5h7pmzy242c2" +target = "christel" +amount = "1000000" + +[[bond]] +source = "albert" +validator = "validator-0" +amount = "20000" \ No newline at end of file diff --git a/genesis/localnet/src/pre-genesis/validator-0/transactions.toml b/genesis/localnet/src/pre-genesis/validator-0/transactions.toml new file mode 100644 index 0000000000..571aa7e523 --- /dev/null +++ b/genesis/localnet/src/pre-genesis/validator-0/transactions.toml @@ -0,0 +1,44 @@ +[[validator_account]] +alias = "validator-0" +dkg_key = "dpktest1vqqqqqqndwmp3cvzfuepzvdh3fmt4dvjwd4vx6ffxk6t4vm6waysng2q8zh3yu8hdnxhwnwq22p8fwaqlyzcggef6k6qv3h3qtyy8c8c686w50v7d0z49ufd6zje46ujzc4ew5z7sdh45d94cvywy2v40yz9jlckh8cksr" +vp = "vp_validator" +commission_rate = "0.05" +max_commission_rate_change = "0.01" +net_address = "127.0.0.1:27656" + +[validator_account.account_key] +pk = "pktest1qpqznlesv6fcgsz9x4rsn7l4dsnp7h5sswvs9atw0zgq3uwp0jll2r42szz" +authorization = "sigtest1qrcy6a095uj6n8ryc9fkfw3hn2jn285g82hdratzrvmg22xp5qdmyr7e9q97t77v4zr8a73x4c3k53pcec3mp4ugt5jqd2w0fvd7nhcp0ajsk4" + +[validator_account.consensus_key] +pk = "pktest1qpk2a5m26cgnrp32l2c2hss03draxwnctu92llhlr6kjesn2kt892k2kklp" +authorization = "sigtest1qzajuey408zf66wgw8x3awd5jx9x249csx63n80clehj238qqpsf9cdr630edqjr8fw95vywlngy368r087sge2xy89qpcsrxvwtm3gr0h6g79" + +[validator_account.protocol_key] +pk = "pktest1qpfv9pj4zmelcg98rtw8wezztzdgpuczuxjmx946klkxajmvvcxjz3vsv63" +authorization = "sigtest1qz0aerskgtzwmgxwgml72zyz4974njfz6s9dl7r399d0v64pzsmctkwe8f28dwyef52hklj6r2fqmeypncvv3qg2x4hg4u3g23kw23qt73uzmy" + +[validator_account.tendermint_node_key] +pk = "pktest1qp7shxxfdkp8ft8xfkgf2vqy7u6effdp82w0cq6aq8575txul33t63dyd68" +authorization = "sigtest1qq87l6zjuswmrfeysxp8j0tu5zlklnswzaqgcuyaq23p49hhvupa57r3rwrh3txfuvq8lh5e7wu7d90js8cn7a7dygs7v5yx2a9ljtqxwl0chr" + +[validator_account.eth_hot_key] +pk = "pktest1qypdzza7uqtklzzs50hhy07h5ru9p0v8y5666wwulqnd3mdpuj0tcxsgck694" +authorization = "sigtest1q9qtzvwlqhnft5jgeyms2w757dlly8rjg4g89593ayhan0fx7uvjzvm7r7ltgsly9tp6783pqpl6ezxty240udrrknxmunymz6lm523qqqwx72ra" + +[validator_account.eth_cold_key] +pk = "pktest1qypzca0n6l890jc83nk5lljuzps04xmfccz3f87xc6ez40t2wvql48s52cx8r" +authorization = "sigtest1q8qmjstc2jtt3dwueepv0njk9te5qpj6z9yj6w0duhagswz80vyws3ywdsy9n49qnjecvnpxwlna3578vn423tgd4rrtss06ejgkj7c5qyv22nm6" + +[[transfer]] +token = "nam" +source = "pktest1qzp22w6trhmxmp2dx5h883c2l684z0e20a9egusmxaat62wvaa4agcthpup" +target = "validator-0" +amount = "200000" +signature = "sigtest1qz8ch7n6jp7g7hvnzkhmprhqc5umd0gatarlttm7gpcdx6g8hmgqffdq67fn6vzcflmym5xej3m5jmfw7nap00s4nlx2zxrlwz0wlfgts9kera" + +[[bond]] +source = "validator-0" +validator = "validator-0" +amount = "100000" +signature = "sigtest1qpss2x7vewnr2sr2wxj0ftldcmvv7v7t7qsr26k2l2hx4gls7y9sksrcq0m9vv2ymdqqxgmmw0dmuwazflxuqt4v2p2wng4endtcfcc0v6f6tj" diff --git a/genesis/localnet/src/pre-genesis/validator-0/validator-wallet.toml b/genesis/localnet/src/pre-genesis/validator-0/validator-wallet.toml new file mode 100644 index 0000000000..b6cc8fea5f --- /dev/null +++ b/genesis/localnet/src/pre-genesis/validator-0/validator-wallet.toml @@ -0,0 +1,11 @@ +account_key = "unencrypted:00dba003e446e56fb3382aaea01f2aab00dd7ea10a8c6725245b9542bc22c51519" +consensus_key = "unencrypted:00c229df6767b29891a1d45285450d8588ee0bc6b61d294bf958001cd7d93fea4e" +eth_cold_key = "unencrypted:0173220e1258baf4bd4851bbdd41e3fc5167ceb0cbcd28d3d1d0d250f7563b2350" +tendermint_node_key = "unencrypted:0036389d415fdb91c224fc6d42a301f545cf9ea879f2ebe548fd31179341e4e318" + +[validator_keys] +protocol_keypair = "ED25519_SK_PREFIX00d95753fab39f87e5c20ecab12690fefa67e97164ca6f17d67aeba50883b93dde" +eth_bridge_keypair = "SECP256K1_SK_PREFIX01263b5b517759fc30c3abed13907b75e779dae70e3675135a788de7c7076efebf" + +[validator_keys.dkg_keypair] +decryption_key = [208, 36, 153, 32, 179, 193, 163, 222, 29, 238, 154, 53, 181, 71, 213, 162, 59, 130, 225, 93, 57, 20, 161, 254, 52, 1, 172, 184, 112, 189, 160, 102] diff --git a/genesis/localnet/src/pre-genesis/wallet.toml b/genesis/localnet/src/pre-genesis/wallet.toml new file mode 100644 index 0000000000..2444ba3f73 --- /dev/null +++ b/genesis/localnet/src/pre-genesis/wallet.toml @@ -0,0 +1,34 @@ +[view_keys] + +[spend_keys] + +[payment_addrs] + +[keys] +albert-key = "unencrypted:0083318ccceac6c08a0177667840b4b93f0e455e45d4c38c28b73b8f8462fbf548" +bertha-key = "unencrypted:0073ed61817720d27784e7a9bca4a60668d763a6f7ecac2d45ed1f241aa5c59e99" +christel-key = "unencrypted:003625b939a58ef60402d7cf8fc04250026cc1ba90cc3028af1ce6b22be857ffc7" +daewon = "unencrypted:00d19e226c0e7d123d79f5908b5948d4c461b66a5f8aa95600c28b55ab6f5dc772" +ester = "unencrypted:01369093e2035d84f72a7e5a17c89b7a938b5d08cc87b2289805e3afcc66ef9a42" +faucet-key = "unencrypted:00548aa8393422b88dce5f4be8ee0320638061c3e0649ada1b0dacbec4c0c75bb2" +validator-0-balance-key = "unencrypted:000b9c8cb8f3ad6b8a387b064a11c0e98189814e9733aa7bb1e802425f6356f98a" + +[addresses] +albert-key = "atest1d9khqw36x56rgvphx5erjwp48y6y2s6y89qnwdzygce5xsfkx9rrsd35xerrjdp3gsunz33n3zytee" +bertha-key = "atest1d9khqw36xvm5zwpjxqm5zwz9x56rydp5gscnwsf4geznjdpegvmrwv29xcmnxvp4x5u5vdjp4wcjdf" +christel-key = "atest1d9khqw36gccrsv348qurzvpcx3p52ve3xu6rys3kxc6yxsjrgymyg32xggmnxvj9g5erzv2rn6pwv6" +daewon = "atest1d9khqw36xuc5gsfegsen2sfsx4q5yvz9xq6yv3psgcunj334g9prvdzzxucnyv2zxqm5xve44x97gg" +ester = "atest1d9khqw36xumnzd3cxccyx33k8ymyy3f5x3z5zd3jx5crsdfcgezyyv6rg3z5vw2xxcenqdpez4uxkf" +faucet-key = "atest1d9khqw368ycns3jp8q6nx329g5myg3zyggu523f589zyzs6x8pq5g3zzx9pr2d3h8qmrjdjyvvty45" +validator-0-balance-key = "atest1d9khqw36geqnxw29x9q5xvj98qmnj3fjgscygs6pg4p52s6z89qnzd3exgmnsv34gfqnj33elp2296" + +[pkhs] +37A8207A8E54244D17A5FE949C671E6730559F6A = "bertha-key" +544075298594ECD9A74DF3CA61F8646F941D91F3 = "albert-key" +71DA9D35A05AB0E04FD0F99F5AB64B7121B07C35 = "daewon" +918FA853EEE6DDDB9EE49DACF8ADDB1B5678696D = "faucet-key" +F0825881084CE31742B664CBCA6DEFB732EE211C = "christel-key" +7716860CF696BE44EA6250858FDB3CDEF9F63049 = "ester" +FA39E1AC2E879E2D0DCAECECB9A16927825BA9F9 = "validator-0-balance-key" + +[address_vp_types] diff --git a/genesis/localnet/tokens.toml b/genesis/localnet/tokens.toml new file mode 100644 index 0000000000..db1da7df9d --- /dev/null +++ b/genesis/localnet/tokens.toml @@ -0,0 +1,64 @@ +# Token accounts with their validity predicates + +[token.NAM] +denom = 6 + +[token.NAM.parameters] +max_reward_rate = "0.1" +kd_gain_nom = "0.1" +kp_gain_nom = "0.1" +locked_ratio_target = "0.6667" + +[token.BTC] +denom = 8 + +[token.BTC.parameters] +max_reward_rate = "0.1" +kd_gain_nom = "0.1" +kp_gain_nom = "0.1" +locked_ratio_target = "0.6667" + +[token.ETH] +denom = 18 + +[token.ETH.parameters] +max_reward_rate = "0.1" +kd_gain_nom = "0.1" +kp_gain_nom = "0.1" +locked_ratio_target = "0.6667" + +[token.DOT] +denom = 10 + +[token.DOT.parameters] +max_reward_rate = "0.1" +kd_gain_nom = "0.1" +kp_gain_nom = "0.1" +locked_ratio_target = "0.6667" + +[token.Schnitzel] +denom = 6 + +[token.Schnitzel.parameters] +max_reward_rate = "0.1" +kd_gain_nom = "0.1" +kp_gain_nom = "0.1" +locked_ratio_target = "0.6667" + +[token.Apfel] +denom = 6 + +[token.Apfel.parameters] +max_reward_rate = "0.1" +kd_gain_nom = "0.1" +kp_gain_nom = "0.1" +locked_ratio_target = "0.6667" + +[token.Kartoffel] +denom = 6 + +[token.Kartoffel.parameters] +max_reward_rate = "0.1" +kd_gain_nom = "0.1" +kp_gain_nom = "0.1" +locked_ratio_target = "0.6667" \ No newline at end of file diff --git a/genesis/localnet/transactions.toml b/genesis/localnet/transactions.toml new file mode 100644 index 0000000000..9a4132d95e --- /dev/null +++ b/genesis/localnet/transactions.toml @@ -0,0 +1,180 @@ +# Transactions pasted from: +# +# 1. `src/pre-genesis/validator-0/transactions.toml` +# 2. `src/pre-genesis/signed-transactions.toml` + +# 1. + +[[validator_account]] +alias = "validator-0" +dkg_key = "dpktest1vqqqqqqndwmp3cvzfuepzvdh3fmt4dvjwd4vx6ffxk6t4vm6waysng2q8zh3yu8hdnxhwnwq22p8fwaqlyzcggef6k6qv3h3qtyy8c8c686w50v7d0z49ufd6zje46ujzc4ew5z7sdh45d94cvywy2v40yz9jlckh8cksr" +vp = "vp_validator" +commission_rate = "0.05" +max_commission_rate_change = "0.01" +net_address = "127.0.0.1:27656" + +[validator_account.account_key] +pk = "pktest1qpqznlesv6fcgsz9x4rsn7l4dsnp7h5sswvs9atw0zgq3uwp0jll2r42szz" +authorization = "sigtest1qrcy6a095uj6n8ryc9fkfw3hn2jn285g82hdratzrvmg22xp5qdmyr7e9q97t77v4zr8a73x4c3k53pcec3mp4ugt5jqd2w0fvd7nhcp0ajsk4" + +[validator_account.consensus_key] +pk = "pktest1qpk2a5m26cgnrp32l2c2hss03draxwnctu92llhlr6kjesn2kt892k2kklp" +authorization = "sigtest1qzajuey408zf66wgw8x3awd5jx9x249csx63n80clehj238qqpsf9cdr630edqjr8fw95vywlngy368r087sge2xy89qpcsrxvwtm3gr0h6g79" + +[validator_account.protocol_key] +pk = "pktest1qpfv9pj4zmelcg98rtw8wezztzdgpuczuxjmx946klkxajmvvcxjz3vsv63" +authorization = "sigtest1qz0aerskgtzwmgxwgml72zyz4974njfz6s9dl7r399d0v64pzsmctkwe8f28dwyef52hklj6r2fqmeypncvv3qg2x4hg4u3g23kw23qt73uzmy" + +[validator_account.tendermint_node_key] +pk = "pktest1qp7shxxfdkp8ft8xfkgf2vqy7u6effdp82w0cq6aq8575txul33t63dyd68" +authorization = "sigtest1qq87l6zjuswmrfeysxp8j0tu5zlklnswzaqgcuyaq23p49hhvupa57r3rwrh3txfuvq8lh5e7wu7d90js8cn7a7dygs7v5yx2a9ljtqxwl0chr" + +[validator_account.eth_hot_key] +pk = "pktest1qypdzza7uqtklzzs50hhy07h5ru9p0v8y5666wwulqnd3mdpuj0tcxsgck694" +authorization = "sigtest1q9qtzvwlqhnft5jgeyms2w757dlly8rjg4g89593ayhan0fx7uvjzvm7r7ltgsly9tp6783pqpl6ezxty240udrrknxmunymz6lm523qqqwx72ra" + +[validator_account.eth_cold_key] +pk = "pktest1qypzca0n6l890jc83nk5lljuzps04xmfccz3f87xc6ez40t2wvql48s52cx8r" +authorization = "sigtest1q8qmjstc2jtt3dwueepv0njk9te5qpj6z9yj6w0duhagswz80vyws3ywdsy9n49qnjecvnpxwlna3578vn423tgd4rrtss06ejgkj7c5qyv22nm6" + +[[transfer]] +token = "nam" +source = "pktest1qzp22w6trhmxmp2dx5h883c2l684z0e20a9egusmxaat62wvaa4agcthpup" +target = "validator-0" +amount = "200000" +signature = "sigtest1qz8ch7n6jp7g7hvnzkhmprhqc5umd0gatarlttm7gpcdx6g8hmgqffdq67fn6vzcflmym5xej3m5jmfw7nap00s4nlx2zxrlwz0wlfgts9kera" + +[[bond]] +source = "validator-0" +validator = "validator-0" +amount = "100000" +signature = "sigtest1qpss2x7vewnr2sr2wxj0ftldcmvv7v7t7qsr26k2l2hx4gls7y9sksrcq0m9vv2ymdqqxgmmw0dmuwazflxuqt4v2p2wng4endtcfcc0v6f6tj" + +# 2. + +[[established_account]] +alias = "albert" +vp = "vp_user" + +[established_account.public_key] +pk = "pktest1qz0aphcsrw37j8fy742cjwhphu9jwx7esd3ad4xxtxrkwv07ff63wnnul44" +authorization = "sigtest1qzq6dwf2a9dq4hp3nmxfgckh55mulzryxsfkqhr9uvfvmtr9wt38lyyvzvfqryxnat2a4ry6hygv957z683dyngu03gse2uvl5ldfccypktkp6" + +[established_account.storage] + +[[established_account]] +alias = "bertha" +vp = "vp_user" + +[established_account.public_key] +pk = "pktest1qpyfnrl6qdqvguah9kknvp9t6ajcrec7fge56pcgaa655zkua3ndsl9tn4a" +authorization = "sigtest1qzt58nd7k2mj647x8x4ydhsjkut7dmsl7yjlnrnwu9kzjdch3cljv6dq05mx2kvwn80kjezh7lz26adc5ksvyn3knufymtkhlmnhg3c8zka3y4" + +[established_account.storage] + +[[established_account]] +alias = "christel" +vp = "vp_user" + +[established_account.public_key] +pk = "pktest1qp6uy52q0fldjxupznuskm69fkuswx3fq3vw9kekzp4enkh5h7pmzy242c2" +authorization = "sigtest1qrf2l6a5u4ywskryhdueudnm50j3kqhujas3mmktenqm89fmlskjnyeskr2tr7js5swmtqqtenkj6ap9xpelx2w40fjjczc4w9xtdggqjtszy4" + +[established_account.storage] + +[[transfer]] +token = "nam" +source = "pktest1qz0aphcsrw37j8fy742cjwhphu9jwx7esd3ad4xxtxrkwv07ff63wnnul44" +target = "albert" +amount = "1000000" +signature = "sigtest1qrxfthuz5yzapdt82d8nc656ffrzkxnkfg6fw8w7e6045vcpn8kpr4c536ug4fs2ddz5823dfd3w4qnhcl40qmtasfccaa99a0hvjcqvyy2l02" + +[[transfer]] +token = "btc" +source = "pktest1qz0aphcsrw37j8fy742cjwhphu9jwx7esd3ad4xxtxrkwv07ff63wnnul44" +target = "albert" +amount = "1000000" +signature = "sigtest1qq826qel8zn5p4dfx25ma45uxeskx0yjlsaxp38uyvehvds6t93c8wkhaydunm4nzz9750dwhkjst00mfrwmu066y3rj6gtk5wjskxq9zvk7ed" + +[[transfer]] +token = "eth" +source = "pktest1qz0aphcsrw37j8fy742cjwhphu9jwx7esd3ad4xxtxrkwv07ff63wnnul44" +target = "albert" +amount = "1000000" +signature = "sigtest1qzghddk0u6kpqaxhskrj8ah69d3hce95wndgvlpuyvuw0h0fnyrnp026ltvtfmfhpzywkf88yyhv80hzkus57s9nn6vn3ljvmkkf7zgq2nzn40" + +[[transfer]] +token = "dot" +source = "pktest1qz0aphcsrw37j8fy742cjwhphu9jwx7esd3ad4xxtxrkwv07ff63wnnul44" +target = "albert" +amount = "1000000" +signature = "sigtest1qqlu8kajh0up5zhewd2h44fhq7zdaapqt9xaj8xp7jmdqpv4dl9d5tesw3wyr9su962rvpkf02x27579vqjk4f4advx7t9ejz74cjrc25puna6" + +[[transfer]] +token = "schnitzel" +source = "pktest1qz0aphcsrw37j8fy742cjwhphu9jwx7esd3ad4xxtxrkwv07ff63wnnul44" +target = "albert" +amount = "1000000" +signature = "sigtest1qpukdd6f7pvtgau9v58xxwrnmv5pjra6527aggce9j8cu6fnekzag2cmmf4w0x0cn76gpth2vw6zcsnw37xjuz96hflqfnk5sznxe6cfd5mjnv" + +[[transfer]] +token = "apfel" +source = "pktest1qz0aphcsrw37j8fy742cjwhphu9jwx7esd3ad4xxtxrkwv07ff63wnnul44" +target = "albert" +amount = "1000000" +signature = "sigtest1qprnsyhlruvw4a6jjs98xdd04gxdsgycywzt79gyyak68gn4f50ppdvdm0q6ns44cheu7s43kal0qsldml8s0zexqk3s9zmglu4l6fszwcqswy" + +[[transfer]] +token = "kartoffel" +source = "pktest1qz0aphcsrw37j8fy742cjwhphu9jwx7esd3ad4xxtxrkwv07ff63wnnul44" +target = "albert" +amount = "1000000" +signature = "sigtest1qpg4y6ltax3qfclguym2kpa0ezlkf4hz4u3h8fc2ndmuzm4zfe26ygq9u6nhjjvj93n5v296x4arkvjk22ygee2fx054ce7ap6gvynq2j2k0vf" + +[[transfer]] +token = "nam" +source = "pktest1qpyfnrl6qdqvguah9kknvp9t6ajcrec7fge56pcgaa655zkua3ndsl9tn4a" +target = "bertha" +amount = "1000000" +signature = "sigtest1qpe3ms76cxvqsa58624ked5ndjr3jr3u7lf9ypalxdh29gajwmsxtzjxzswk0xmza90u47tgksr0d7enpd730ps7fq5u76l7ekxeyvgfnl0qxj" + +[[transfer]] +token = "btc" +source = "pktest1qpyfnrl6qdqvguah9kknvp9t6ajcrec7fge56pcgaa655zkua3ndsl9tn4a" +target = "bertha" +amount = "1000000" +signature = "sigtest1qqq4yc6s3h4ysezcmgsdgyfsqu8mmrwwc8a358sm864zehx74ums8fmgmkyyr3wz9qqrc5fagwrw0a4mgqkkleumc0u6ynshj5ns0tcdrygupg" + +[[transfer]] +token = "eth" +source = "pktest1qpyfnrl6qdqvguah9kknvp9t6ajcrec7fge56pcgaa655zkua3ndsl9tn4a" +target = "bertha" +amount = "1000000" +signature = "sigtest1qzyqkdxt3cxf0huv0hzskqmuthw38s3kj2sx50znzc68gzx64hrd3hkyx0g27pruwa9fv42suw3k8gkfaelcp4ymnvfw7ltx0mz2z9qr2lwwcs" + +[[transfer]] +token = "nam" +source = "pktest1qp6uy52q0fldjxupznuskm69fkuswx3fq3vw9kekzp4enkh5h7pmzy242c2" +target = "christel" +amount = "1000000" +signature = "sigtest1qran2jh0dwuwchh45snhk3p9uyxqym57eg527pcjeekwhvpr5j0fy6dte7x3v2dwwc6vqmv49drpvmk6uhzlt2p8rqx0th3h7smf8rqrcpfxc7" + +[[transfer]] +token = "btc" +source = "pktest1qp6uy52q0fldjxupznuskm69fkuswx3fq3vw9kekzp4enkh5h7pmzy242c2" +target = "christel" +amount = "1000000" +signature = "sigtest1qrxp6p56qtvsyp7jfs4jru3mkfv6l4t7hpguur72579pqjaexdry9turgmzwzj6qmua3s2y2a4krtuxwaag66j4dpz44aaqksy30tfcxmnhw2k" + +[[transfer]] +token = "eth" +source = "pktest1qp6uy52q0fldjxupznuskm69fkuswx3fq3vw9kekzp4enkh5h7pmzy242c2" +target = "christel" +amount = "1000000" +signature = "sigtest1qrtrec6mps9p7zhmm7sywxwc7zdm32umt0zdwpuflrkh6952rx74amnsf9hn0yughs4e6czgpdmwsmujvkr4ptk2x24vtc8phz0s2asvtyy9pf" + +[[bond]] +source = "albert" +validator = "validator-0" +amount = "20000" +signature = "sigtest1qpx0kc8rl966vnnswfwhcwxh5vv69p7lev2apc2yephag978gqg5903kuw6dugg9c6pg5jdw048umk72ntympphz0dka74aspldslaqr3q7eqr" diff --git a/genesis/localnet/validity-predicates.toml b/genesis/localnet/validity-predicates.toml new file mode 100644 index 0000000000..a0c2570121 --- /dev/null +++ b/genesis/localnet/validity-predicates.toml @@ -0,0 +1,17 @@ +# WASM Validity predicate that can be used for genesis accounts + +# Implicit VP +[wasm.vp_implicit] +filename = "vp_implicit.wasm" + +# Default user VP in established accounts +[wasm.vp_user] +filename = "vp_user.wasm" + +# Default validator VP +[wasm.vp_validator] +filename = "vp_validator.wasm" + +# MASP VP +[wasm.vp_masp] +filename = "vp_masp.wasm" diff --git a/genesis/starter/README.md b/genesis/starter/README.md new file mode 100644 index 0000000000..ef58236340 --- /dev/null +++ b/genesis/starter/README.md @@ -0,0 +1,71 @@ +# Starter genesis templates + +This directory contains genesis templates for a minimum configuration with a single token account, which can be used as a starter for setting up a new chain. + +If you're modifying any of the files here, you can run this to ensure that the changes are valid: + +```shell +cargo watch -x "test test_validate_starter_genesis_templates" +``` + +In order to be able to run it, the following has to be added: + +1. At least one key with a positive native [token balance](#token-balances) +2. At least one [validator account](#validator-accounts), with some native tokens transferred from the key, some of which have to be self-bonded in PoS to amount to a positive voting power + +## Token balances + +We'll generate a key and give it some token balance. + +To generate a new key in a pre-genesis wallet (before the chain is setup), you can use e.g.: + +```shell +namada wallet key gen --pre-genesis --alias "my-key" +``` + +This will print your public key: + +```shell +Successfully added a key and an address with alias: "my-key". +Public key: pktest1qz5ywdn47sdm8s7rkzjl5dud0k9c9ndd5agn4gu0u0ryrmtmyuxmk948q0p +``` + +The public key can then be given some tokens in the [balances.toml file](balances.toml) with e.g.: + +```toml +[token.NAM] +pktest1qz5ywdn47sdm8s7rkzjl5dud0k9c9ndd5agn4gu0u0ryrmtmyuxmk948q0p = 1_337_707.50 +``` + +## Validator accounts + +For this step, you'll need to have a key with some native [token balance](#token-balances), from which you can sign the validator account creation genesis transaction. + +To generate a new validator pre-genesis wallet and produce signed transactions with it, use e.g.: + +```shell +namada client utils \ + init-genesis-validator \ + --source "my-key" \ + --alias "my-validator" \ + --net-address "127.0.0.1:26656" \ + --commission-rate 0.05 \ + --max-commission-rate-change 0.01 \ + --transfer-from-source-amount 1337707.50 \ + --self-bond-amount 1000000 +``` + +This will print the validator transactions that can be added to the [transactions.toml file](transactions.toml). + +## Initialize the chain + +This is sufficient minimal configuration to initialize the chain with the single genesis validator. All that's left is to pick a chain ID prefix and genesis time: + +```shell +namada client utils \ + init-network \ + --chain-prefix "my-chain" \ + --genesis-time "2021-12-31T00:00:00Z" \ + --templates-path "path/to/templates" \ + --wasm-checksums-path "path/to/wasm/checksums.json" +``` diff --git a/genesis/starter/balances.toml b/genesis/starter/balances.toml new file mode 100644 index 0000000000..2f1aad4022 --- /dev/null +++ b/genesis/starter/balances.toml @@ -0,0 +1,2 @@ +[token.NAM] +# assign balance to public keys diff --git a/genesis/starter/parameters.toml b/genesis/starter/parameters.toml new file mode 100644 index 0000000000..86714f827c --- /dev/null +++ b/genesis/starter/parameters.toml @@ -0,0 +1,90 @@ +# General protocol parameters. +[parameters] +native_token = "NAM" +# Minimum number of blocks in an epoch. +min_num_of_blocks = 4 +# Maximum expected time per block (in seconds). +max_expected_time_per_block = 30 +# Max payload size, in bytes, for a tx batch proposal. +max_proposal_bytes = 22020096 +# vp whitelist +vp_whitelist = [] +# tx whitelist +tx_whitelist = [] +# Implicit VP WASM name +implicit_vp = "vp_implicit" +# Expected number of epochs per year (also sets the min duration of an epoch in seconds) +epochs_per_year = 31_536_000 +# The P gain factor in the Proof of Stake rewards controller +pos_gain_p = "0.1" +# The D gain factor in the Proof of Stake rewards controller +pos_gain_d = "0.1" +# Maximum number of signature per transaction +max_signatures_per_transaction = 15 +# Max gas for block +max_block_gas = 20000000 +# Fee unshielding gas limit +fee_unshielding_gas_limit = 20000 +# Fee unshielding descriptions limit +fee_unshielding_descriptions_limit = 15 + +# Map of the cost per gas unit for every token allowed for fee payment +[parameters.minimum_gas_price] +nam = "0.000001" + +# Proof of stake parameters. +[pos_params] +# Maximum number of active validators. +max_validator_slots = 128 +# Pipeline length (in epochs). Any change in the validator set made in +# epoch 'n' will become active in epoch 'n + pipeline_len'. +pipeline_len = 2 +# Unbonding length (in epochs). Validators may have their stake slashed +# for a fault in epoch 'n' up through epoch 'n + unbonding_len'. +unbonding_len = 3 +# Votes per fundamental staking token (namnam) +tm_votes_per_token = "1" +# Reward for proposing a block. +block_proposer_reward = "0.125" +# Reward for voting on a block. +block_vote_reward = "0.1" +# Maximum inflation rate per annum (10%) +max_inflation_rate = "0.1" +# Targeted ratio of staked tokens to total tokens in the supply +target_staked_ratio = "0.6667" +# Portion of a validator's stake that should be slashed on a duplicate +# vote. +duplicate_vote_min_slash_rate = "0.001" +# Portion of a validator's stake that should be slashed on a light +# client attack. +light_client_attack_min_slash_rate = "0.001" +# Number of epochs above and below (separately) the current epoch to +# consider when doing cubic slashing +cubic_slashing_window_length = 1 +# The minimum amount of bonded tokens that a validator needs to be in +# either the `consensus` or `below_capacity` validator sets +validator_stake_threshold = "1" + +# Governance parameters. +[gov_params] +# minimum amount of nam token to lock +min_proposal_fund = 500 +# proposal code size in bytes +max_proposal_code_size = 300000 +# min proposal period length in epochs +min_proposal_voting_period = 3 +# max proposal period length in epochs +max_proposal_period = 27 +# maximum number of characters in the proposal content +max_proposal_content_size = 10000 +# minimum epochs between end and grace epoch +min_proposal_grace_epochs = 6 + +# Public goods funding parameters +[pgf_params] +# Initial set of stewards +stewards = [] +# The pgf funding inflation rate +pgf_inflation_rate = "0.1" +# The pgf stewards inflation rate +stewards_inflation_rate = "0.01" diff --git a/genesis/starter/tokens.toml b/genesis/starter/tokens.toml new file mode 100644 index 0000000000..d0f9d44d44 --- /dev/null +++ b/genesis/starter/tokens.toml @@ -0,0 +1,11 @@ +# Token accounts with their validity predicates + +[token.NAM] +vp = "vp_token" +denom = 6 + +[token.NAM.parameters] +max_reward_rate = "0.1" +kd_gain_nom = "0.1" +kp_gain_nom = "0.1" +locked_ratio_target = "0.6667" \ No newline at end of file diff --git a/genesis/starter/transactions.toml b/genesis/starter/transactions.toml new file mode 100644 index 0000000000..e9406c2f03 --- /dev/null +++ b/genesis/starter/transactions.toml @@ -0,0 +1 @@ +# Genesis transactions \ No newline at end of file diff --git a/genesis/starter/validity-predicates.toml b/genesis/starter/validity-predicates.toml new file mode 100644 index 0000000000..863ec7ec38 --- /dev/null +++ b/genesis/starter/validity-predicates.toml @@ -0,0 +1,22 @@ +# WASM Validity predicate that can be used for genesis accounts + +# Implicit VP +[wasm.vp_implicit] +filename = "vp_implicit.wasm" + +# Default user VP in established accounts +[wasm.vp_user] +filename = "vp_user.wasm" + +# Default validator VP +[wasm.vp_validator] +filename = "vp_validator.wasm" + +# Token VP +[wasm.vp_token] +filename = "vp_token.wasm" + +# MASP VP +[wasm.vp_masp] +filename = "vp_masp.wasm" + diff --git a/proof_of_stake/src/epoched.rs b/proof_of_stake/src/epoched.rs index 796827de1f..c35b310d0c 100644 --- a/proof_of_stake/src/epoched.rs +++ b/proof_of_stake/src/epoched.rs @@ -1399,7 +1399,7 @@ mod test { let mut s = TestWlStorage::default(); let gov_params = namada_core::ledger::governance::parameters::GovernanceParameters::default(); gov_params.init_storage(&mut s)?; - crate::init_genesis( + crate::test_utils::init_genesis_helper( &mut s, &PosParams::default(), [GenesisValidator { diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 0aa24142fc..f977ff4a0c 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -60,11 +60,11 @@ use types::{ BondsAndUnbondsDetail, BondsAndUnbondsDetails, CommissionRates, ConsensusValidator, ConsensusValidatorSet, ConsensusValidatorSets, DelegatorRedelegatedBonded, DelegatorRedelegatedUnbonded, - EagerRedelegatedBondsMap, EpochedSlashes, GenesisValidator, - IncomingRedelegations, OutgoingRedelegations, Position, - RedelegatedBondsOrUnbonds, RedelegatedTokens, ReverseOrdTokenAmount, - RewardsAccumulator, RewardsProducts, Slash, SlashType, SlashedAmount, - Slashes, TotalConsensusStakes, TotalDeltas, TotalRedelegatedBonded, + EagerRedelegatedBondsMap, EpochedSlashes, IncomingRedelegations, + OutgoingRedelegations, Position, RedelegatedBondsOrUnbonds, + RedelegatedTokens, ReverseOrdTokenAmount, RewardsAccumulator, + RewardsProducts, Slash, SlashType, SlashedAmount, Slashes, + TotalConsensusStakes, TotalDeltas, TotalRedelegatedBonded, TotalRedelegatedUnbonded, UnbondDetails, Unbonds, ValidatorAddresses, ValidatorConsensusKeys, ValidatorDeltas, ValidatorEthColdKeys, ValidatorEthHotKeys, ValidatorPositionAddresses, ValidatorProtocolKeys, @@ -298,117 +298,34 @@ pub fn delegator_redelegated_unbonds_handle( pub fn init_genesis( storage: &mut S, params: &OwnedPosParams, - validators: impl Iterator + Clone, - current_epoch: namada_core::types::storage::Epoch, + current_epoch: Epoch, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, { tracing::debug!("Initializing PoS genesis"); write_pos_params(storage, params)?; - let params = read_non_pos_owned_params(storage, params.clone())?; - let mut total_bonded = token::Amount::zero(); consensus_validator_set_handle().init(storage, current_epoch)?; below_capacity_validator_set_handle().init(storage, current_epoch)?; validator_set_positions_handle().init(storage, current_epoch)?; validator_addresses_handle().init(storage, current_epoch)?; + tracing::debug!("Finished genesis"); + Ok(()) +} - for GenesisValidator { - address, - tokens, - consensus_key, - protocol_key, - eth_cold_key, - eth_hot_key, - commission_rate, - max_commission_rate_change, - } in validators - { - // This will fail if the key is already being used - the uniqueness must - // be enforced in the genesis configuration to prevent it - try_insert_consensus_key(storage, &consensus_key)?; - - total_bonded += tokens; - - // Insert the validator into a validator set and write its epoched - // validator data - insert_validator_into_validator_set( - storage, - ¶ms, - &address, - tokens, - current_epoch, - 0, - )?; - - validator_addresses_handle() - .at(¤t_epoch) - .insert(storage, address.clone())?; - - // Write other validator data to storage - write_validator_address_raw_hash(storage, &address, &consensus_key)?; - write_validator_max_commission_rate_change( - storage, - &address, - max_commission_rate_change, - )?; - validator_consensus_key_handle(&address).init_at_genesis( - storage, - consensus_key, - current_epoch, - )?; - validator_protocol_key_handle(&address).init_at_genesis( - storage, - protocol_key, - current_epoch, - )?; - validator_eth_hot_key_handle(&address).init_at_genesis( - storage, - eth_hot_key, - current_epoch, - )?; - validator_eth_cold_key_handle(&address).init_at_genesis( - storage, - eth_cold_key, - current_epoch, - )?; - validator_deltas_handle(&address).init_at_genesis( - storage, - tokens.change(), - current_epoch, - )?; - bond_handle(&address, &address).init_at_genesis( - storage, - tokens, - current_epoch, - )?; - total_bonded_handle(&address).init_at_genesis( - storage, - tokens, - current_epoch, - )?; - validator_commission_rate_handle(&address).init_at_genesis( - storage, - commission_rate, - current_epoch, - )?; - } - - // Store the total consensus validator stake to storage - store_total_consensus_stake(storage, current_epoch)?; - - // Write total deltas to storage - total_deltas_handle().init_at_genesis( - storage, - token::Change::from(total_bonded), - current_epoch, - )?; +/// new init genesis +pub fn copy_genesis_validator_sets( + storage: &mut S, + params: &OwnedPosParams, + current_epoch: Epoch, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + let params = read_non_pos_owned_params(storage, params.clone())?; - // Credit bonded token amount to the PoS account - let staking_token = staking_token_address(storage); - token::credit_tokens(storage, &staking_token, &ADDRESS, total_bonded)?; - // Copy the genesis validator set into the pipeline epoch as well + // Copy the genesis validator sets up to the pipeline epoch for epoch in (current_epoch.next()).iter_range(params.pipeline_len) { copy_validator_sets_and_positions( storage, @@ -416,10 +333,8 @@ where current_epoch, epoch, )?; + store_total_consensus_stake(storage, epoch)?; } - - tracing::debug!("Genesis initialized"); - Ok(()) } @@ -600,15 +515,17 @@ where /// Add or remove PoS validator's stake delta value pub fn update_validator_deltas( storage: &mut S, + params: &OwnedPosParams, validator: &Address, delta: token::Change, current_epoch: namada_core::types::storage::Epoch, - offset: u64, + offset_opt: Option, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, { let handle = validator_deltas_handle(validator); + let offset = offset_opt.unwrap_or(params.pipeline_len); let val = handle .get_delta_val(storage, current_epoch + offset)? .unwrap_or_default(); @@ -781,14 +698,16 @@ where /// Note: for EpochedDelta, write the value to change storage by pub fn update_total_deltas( storage: &mut S, + params: &OwnedPosParams, delta: token::Change, current_epoch: namada_core::types::storage::Epoch, - offset: u64, + offset_opt: Option, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, { let handle = total_deltas_handle(); + let offset = offset_opt.unwrap_or(params.pipeline_len); let val = handle .get_delta_val(storage, current_epoch + offset)? .unwrap_or_default(); @@ -848,6 +767,7 @@ pub fn bond_tokens( validator: &Address, amount: token::Amount, current_epoch: Epoch, + offset_opt: Option, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, @@ -861,9 +781,8 @@ where } let params = read_pos_params(storage)?; - let pipeline_epoch = current_epoch + params.pipeline_len; - - // Check that the source is not a validator + let offset = offset_opt.unwrap_or(params.pipeline_len); + let offset_epoch = current_epoch + offset; if let Some(source) = source { if source != validator && is_validator(storage, source)? { return Err( @@ -874,7 +793,7 @@ where // Check that the validator is actually a validator let validator_state_handle = validator_state_handle(validator); - let state = validator_state_handle.get(storage, pipeline_epoch, ¶ms)?; + let state = validator_state_handle.get(storage, offset_epoch, ¶ms)?; if state.is_none() { return Err(BondError::NotAValidator(validator.clone()).into()); } @@ -887,7 +806,7 @@ where // Check that validator is not inactive at anywhere between the current // epoch and pipeline offset - for epoch in current_epoch.iter_range(params.pipeline_len) { + for epoch in current_epoch.iter_range(offset) { if let Some(ValidatorState::Inactive) = validator_state_handle.get(storage, epoch, ¶ms)? { @@ -901,13 +820,8 @@ where } // Initialize or update the bond at the pipeline offset - bond_handle.add(storage, amount, current_epoch, params.pipeline_len)?; - total_bonded_handle.add( - storage, - amount, - current_epoch, - params.pipeline_len, - )?; + bond_handle.add(storage, amount, current_epoch, offset)?; + total_bonded_handle.add(storage, amount, current_epoch, offset)?; if tracing::level_enabled!(tracing::Level::DEBUG) { let bonds = find_bonds(storage, source, validator)?; @@ -919,7 +833,7 @@ where // must be no changes to the validator set. Check at the pipeline epoch. let is_jailed_at_pipeline = matches!( validator_state_handle - .get(storage, pipeline_epoch, ¶ms)? + .get(storage, offset_epoch, ¶ms)? .unwrap(), ValidatorState::Jailed ); @@ -929,24 +843,27 @@ where ¶ms, validator, amount.change(), - pipeline_epoch, + current_epoch, + offset_opt, )?; } // Update the validator and total deltas update_validator_deltas( storage, + ¶ms, validator, amount.change(), current_epoch, - params.pipeline_len, + offset_opt, )?; update_total_deltas( storage, + ¶ms, amount.change(), current_epoch, - params.pipeline_len, + offset_opt, )?; // Transfer the bonded tokens from the source to PoS @@ -1068,7 +985,8 @@ fn update_validator_set( params: &PosParams, validator: &Address, token_change: token::Change, - epoch: Epoch, + current_epoch: Epoch, + offset: Option, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, @@ -1076,7 +994,8 @@ where if token_change.is_zero() { return Ok(()); } - // let pipeline_epoch = current_epoch + params.pipeline_len; + let offset = offset.unwrap_or(params.pipeline_len); + let epoch = current_epoch + offset; tracing::debug!( "Update epoch for validator set: {epoch}, validator: {validator}" ); @@ -1140,8 +1059,8 @@ where validator_state_handle(validator).set( storage, ValidatorState::BelowThreshold, - epoch, - 0, + current_epoch, + offset, )?; // Remove the validator's position from storage @@ -1177,8 +1096,8 @@ where validator_state_handle(&removed_max_below_capacity).set( storage, ValidatorState::Consensus, - epoch, - 0, + current_epoch, + offset, )?; } } else if tokens_post < max_below_capacity_validator_amount { @@ -1212,8 +1131,8 @@ where validator_state_handle(&removed_max_below_capacity).set( storage, ValidatorState::Consensus, - epoch, - 0, + current_epoch, + offset, )?; // Insert the current validator into the below-capacity set @@ -1226,8 +1145,8 @@ where validator_state_handle(validator).set( storage, ValidatorState::BelowCapacity, - epoch, - 0, + current_epoch, + offset, )?; } else { tracing::debug!("Validator remains in consensus set"); @@ -1270,7 +1189,8 @@ where validator, tokens_post, min_consensus_validator_amount, - epoch, + current_epoch, + offset, &consensus_val_handle, &below_capacity_val_handle, )?; @@ -1286,8 +1206,8 @@ where validator_state_handle(validator).set( storage, ValidatorState::BelowCapacity, - epoch, - 0, + current_epoch, + offset, )?; } else { // The current validator is demoted to the below-threshold set @@ -1298,8 +1218,8 @@ where validator_state_handle(validator).set( storage, ValidatorState::BelowThreshold, - epoch, - 0, + current_epoch, + offset, )?; // Remove the validator's position from storage @@ -1309,9 +1229,12 @@ where } } } else { - // If there is no position at pipeline offset, then the validator must - // be in the below-threshold set - debug_assert!(tokens_pre < params.validator_stake_threshold); + // At non-zero offset (0 is genesis only) + if offset > 0 { + // If there is no position at pipeline offset, then the validator + // must be in the below-threshold set + debug_assert!(tokens_pre < params.validator_stake_threshold); + } tracing::debug!("Target validator is below-threshold"); // Move the validator into the appropriate set @@ -1330,8 +1253,8 @@ where validator_state_handle(validator).set( storage, ValidatorState::Consensus, - epoch, - 0, + current_epoch, + offset, )?; } else { let min_consensus_validator_amount = @@ -1352,7 +1275,8 @@ where validator, tokens_post, min_consensus_validator_amount, - epoch, + current_epoch, + offset, &consensus_val_handle, &below_capacity_val_handle, )?; @@ -1371,8 +1295,8 @@ where validator_state_handle(validator).set( storage, ValidatorState::BelowCapacity, - epoch, - 0, + current_epoch, + offset, )?; } } @@ -1387,7 +1311,8 @@ fn insert_into_consensus_and_demote_to_below_cap( validator: &Address, tokens_post: token::Amount, min_consensus_amount: token::Amount, - epoch: Epoch, + current_epoch: Epoch, + offset: u64, consensus_set: &ConsensusValidatorSet, below_capacity_set: &BelowCapacityValidatorSet, ) -> storage_api::Result<()> @@ -1403,35 +1328,35 @@ where .remove(storage, &last_position_of_min_consensus_vals)? .expect("There must be always be at least 1 consensus validator"); - // let pipeline_epoch = current_epoch + params.pipeline_len; + let offset_epoch = current_epoch + offset; // Insert the min consensus validator into the below-capacity // set insert_validator_into_set( &below_capacity_set.at(&min_consensus_amount.into()), storage, - &epoch, + &offset_epoch, &removed_min_consensus, )?; validator_state_handle(&removed_min_consensus).set( storage, ValidatorState::BelowCapacity, - epoch, - 0, + current_epoch, + offset, )?; // Insert the current validator into the consensus set insert_validator_into_set( &consensus_set.at(&tokens_post), storage, - &epoch, + &offset_epoch, validator, )?; validator_state_handle(validator).set( storage, ValidatorState::Consensus, - epoch, - 0, + current_epoch, + offset, )?; Ok(()) } @@ -2048,23 +1973,26 @@ where ¶ms, validator, change_after_slashing, - pipeline_epoch, + current_epoch, + None, )?; } // Update the validator and total deltas at the pipeline offset update_validator_deltas( storage, + ¶ms, validator, change_after_slashing, current_epoch, - params.pipeline_len, + None, )?; update_total_deltas( storage, + ¶ms, change_after_slashing, current_epoch, - params.pipeline_len, + None, )?; if tracing::level_enabled!(tracing::Level::DEBUG) { @@ -2761,6 +2689,8 @@ pub struct BecomeValidator<'a, S> { pub commission_rate: Dec, /// Max commission rate change. pub max_commission_rate_change: Dec, + /// Optional offset to use instead of pipeline offset + pub offset_opt: Option, } /// Initialize data for a new validator. @@ -2781,12 +2711,14 @@ where current_epoch, commission_rate, max_commission_rate_change, + offset_opt, } = args; + let offset = offset_opt.unwrap_or(params.pipeline_len); // This will fail if the key is already being used try_insert_consensus_key(storage, consensus_key)?; - let pipeline_epoch = current_epoch + params.pipeline_len; + let pipeline_epoch = current_epoch + offset; validator_addresses_handle() .at(&pipeline_epoch) .insert(storage, address.clone())?; @@ -2804,37 +2736,37 @@ where storage, consensus_key.clone(), current_epoch, - params.pipeline_len, + offset, )?; validator_protocol_key_handle(address).set( storage, protocol_key.clone(), current_epoch, - params.pipeline_len, + offset, )?; validator_eth_hot_key_handle(address).set( storage, eth_hot_key.clone(), current_epoch, - params.pipeline_len, + offset, )?; validator_eth_cold_key_handle(address).set( storage, eth_cold_key.clone(), current_epoch, - params.pipeline_len, + offset, )?; validator_commission_rate_handle(address).set( storage, commission_rate, current_epoch, - params.pipeline_len, + offset, )?; validator_deltas_handle(address).set( storage, token::Change::zero(), current_epoch, - params.pipeline_len, + offset, )?; // The validator's stake at initialization is 0, so its state is immediately @@ -2843,7 +2775,16 @@ where storage, ValidatorState::BelowThreshold, current_epoch, - params.pipeline_len, + offset, + )?; + + insert_validator_into_validator_set( + storage, + params, + address, + token::Amount::zero(), + current_epoch, + offset, )?; Ok(()) @@ -4526,6 +4467,7 @@ where &validator, -slash_amount.change(), epoch, + Some(0), )?; } } @@ -4536,12 +4478,19 @@ where update_validator_deltas( storage, + ¶ms, &validator, -slash_delta.change(), epoch, - 0, + Some(0), + )?; + update_total_deltas( + storage, + ¶ms, + -slash_delta.change(), + epoch, + Some(0), )?; - update_total_deltas(storage, -slash_delta.change(), epoch, 0)?; } // TODO: should we clear some storage here as is done in Quint?? @@ -5361,42 +5310,116 @@ where ¶ms, dest_validator, amount_after_slashing.change(), - pipeline_epoch, + current_epoch, + None, )?; } // Update deltas update_validator_deltas( storage, + ¶ms, dest_validator, amount_after_slashing.change(), current_epoch, - params.pipeline_len, + None, )?; update_total_deltas( storage, + ¶ms, amount_after_slashing.change(), current_epoch, - params.pipeline_len, + None, )?; Ok(()) } -/// Init PoS genesis wrapper helper that also initializes gov params that are -/// used in PoS with default values. #[cfg(any(test, feature = "testing"))] -pub fn test_init_genesis( - storage: &mut S, - owned: OwnedPosParams, - validators: impl Iterator + Clone, - current_epoch: namada_core::types::storage::Epoch, -) -> storage_api::Result -where - S: StorageRead + StorageWrite, -{ - let gov_params = namada_core::ledger::governance::parameters::GovernanceParameters::default(); - gov_params.init_storage(storage)?; - crate::init_genesis(storage, &owned, validators, current_epoch)?; - crate::read_non_pos_owned_params(storage, owned) +/// PoS related utility functions to help set up tests. +pub mod test_utils { + use namada_core::ledger::storage_api; + use namada_core::ledger::storage_api::token::credit_tokens; + use namada_core::ledger::storage_api::{StorageRead, StorageWrite}; + + use super::*; + use crate::parameters::PosParams; + use crate::types::GenesisValidator; + + /// Helper function to intialize storage with PoS data + /// about validators for tests. + pub fn init_genesis_helper( + storage: &mut S, + params: &PosParams, + validators: impl Iterator, + current_epoch: namada_core::types::storage::Epoch, + ) -> storage_api::Result<()> + where + S: StorageRead + StorageWrite, + { + init_genesis(storage, params, current_epoch)?; + for GenesisValidator { + address, + consensus_key, + protocol_key, + eth_cold_key, + eth_hot_key, + commission_rate, + max_commission_rate_change, + tokens, + } in validators + { + become_validator(BecomeValidator { + storage, + params, + address: &address, + consensus_key: &consensus_key, + protocol_key: &protocol_key, + eth_cold_key: ð_cold_key, + eth_hot_key: ð_hot_key, + current_epoch, + commission_rate, + max_commission_rate_change, + offset_opt: Some(0), + })?; + // Credit token amount to be bonded to the validator address so it + // can be bonded + let staking_token = staking_token_address(storage); + credit_tokens(storage, &staking_token, &address, tokens)?; + + bond_tokens( + storage, + None, + &address, + tokens, + current_epoch, + Some(0), + )?; + } + // Store the total consensus validator stake to storage + store_total_consensus_stake(storage, current_epoch)?; + + // Copy validator sets and positions + copy_genesis_validator_sets(storage, params, current_epoch)?; + + Ok(()) + } + + /// Init PoS genesis wrapper helper that also initializes gov params that + /// are used in PoS with default values. + pub fn test_init_genesis( + storage: &mut S, + owned: OwnedPosParams, + validators: impl Iterator + Clone, + current_epoch: namada_core::types::storage::Epoch, + ) -> storage_api::Result + where + S: StorageRead + StorageWrite, + { + let gov_params = namada_core::ledger::governance::parameters::GovernanceParameters::default(); + gov_params.init_storage(storage)?; + let params = crate::read_non_pos_owned_params(storage, owned)?; + init_genesis_helper(storage, ¶ms, validators, current_epoch)?; + Ok(params) + } } diff --git a/proof_of_stake/src/tests.rs b/proof_of_stake/src/tests.rs index ad0a2cccd9..35c1d04fae 100644 --- a/proof_of_stake/src/tests.rs +++ b/proof_of_stake/src/tests.rs @@ -41,6 +41,7 @@ use test_log::test; use crate::epoched::DEFAULT_NUM_PAST_EPOCHS; use crate::parameters::testing::arb_pos_params; use crate::parameters::{OwnedPosParams, PosParams}; +use crate::test_utils::test_init_genesis; use crate::types::{ into_tm_voting_power, BondDetails, BondId, BondsAndUnbondsDetails, ConsensusValidator, EagerRedelegatedBondsMap, GenesisValidator, Position, @@ -64,11 +65,10 @@ use crate::{ read_consensus_validator_set_addresses_with_stake, read_total_stake, read_validator_deltas_value, read_validator_stake, slash, slash_redelegation, slash_validator, slash_validator_redelegation, - staking_token_address, store_total_consensus_stake, test_init_genesis, - total_bonded_handle, total_deltas_handle, total_unbonded_handle, - unbond_handle, unbond_tokens, unjail_validator, update_validator_deltas, - update_validator_set, validator_consensus_key_handle, - validator_incoming_redelegations_handle, + staking_token_address, store_total_consensus_stake, total_bonded_handle, + total_deltas_handle, total_unbonded_handle, unbond_handle, unbond_tokens, + unjail_validator, update_validator_deltas, update_validator_set, + validator_consensus_key_handle, validator_incoming_redelegations_handle, validator_outgoing_redelegations_handle, validator_set_positions_handle, validator_set_update_tendermint, validator_slashes_handle, validator_state_handle, validator_total_redelegated_bonded_handle, @@ -390,6 +390,7 @@ fn test_bonds_aux(params: OwnedPosParams, validators: Vec) { &validator.address, amount_self_bond, current_epoch, + None, ) .unwrap(); @@ -501,6 +502,7 @@ fn test_bonds_aux(params: OwnedPosParams, validators: Vec) { &validator.address, amount_del, current_epoch, + None, ) .unwrap(); let val_stake_pre = read_validator_stake( @@ -949,6 +951,7 @@ fn test_become_validator_aux( commission_rate: Dec::new(5, 2).expect("Dec creation failed"), max_commission_rate_change: Dec::new(5, 2) .expect("Dec creation failed"), + offset_opt: None, }) .unwrap(); assert!(is_validator(&s, &new_validator).unwrap()); @@ -967,7 +970,8 @@ fn test_become_validator_aux( let staking_token = staking_token_address(&s); let amount = token::Amount::from_uint(100_500_000, 0).unwrap(); credit_tokens(&mut s, &staking_token, &new_validator, amount).unwrap(); - bond_tokens(&mut s, None, &new_validator, amount, current_epoch).unwrap(); + bond_tokens(&mut s, None, &new_validator, amount, current_epoch, None) + .unwrap(); // Check the bond delta let bond_handle = bond_handle(&new_validator, &new_validator); @@ -1318,14 +1322,8 @@ fn test_validator_sets() { ) .unwrap(); - update_validator_deltas( - s, - addr, - stake.change(), - epoch, - params.pipeline_len, - ) - .unwrap(); + update_validator_deltas(s, ¶ms, addr, stake.change(), epoch, None) + .unwrap(); // Set their consensus key (needed for // `validator_set_update_tendermint` fn) @@ -1547,20 +1545,15 @@ fn test_validator_sets() { // Because `update_validator_set` and `update_validator_deltas` are // effective from pipeline offset, we use pipeline epoch for the rest of the // checks - update_validator_set( - &mut s, - ¶ms, - &val1, - -unbond.change(), - pipeline_epoch, - ) - .unwrap(); + update_validator_set(&mut s, ¶ms, &val1, -unbond.change(), epoch, None) + .unwrap(); update_validator_deltas( &mut s, + ¶ms, &val1, -unbond.change(), epoch, - params.pipeline_len, + None, ) .unwrap(); // Epoch 6 @@ -1748,16 +1741,10 @@ fn test_validator_sets() { let bond = token::Amount::from_uint(500_000, 0).unwrap(); let stake6 = stake6 + bond; println!("val6 {val6} new stake {}", stake6.to_string_native()); - update_validator_set(&mut s, ¶ms, &val6, bond.change(), pipeline_epoch) + update_validator_set(&mut s, ¶ms, &val6, bond.change(), epoch, None) + .unwrap(); + update_validator_deltas(&mut s, ¶ms, &val6, bond.change(), epoch, None) .unwrap(); - update_validator_deltas( - &mut s, - &val6, - bond.change(), - epoch, - params.pipeline_len, - ) - .unwrap(); let val6_bond_epoch = pipeline_epoch; let consensus_vals: Vec<_> = consensus_validator_set_handle() @@ -1999,14 +1986,8 @@ fn test_validator_sets_swap() { ) .unwrap(); - update_validator_deltas( - s, - addr, - stake.change(), - epoch, - params.pipeline_len, - ) - .unwrap(); + update_validator_deltas(s, ¶ms, addr, stake.change(), epoch, None) + .unwrap(); // Set their consensus key (needed for // `validator_set_update_tendermint` fn) @@ -2035,37 +2016,27 @@ fn test_validator_sets_swap() { assert_eq!(into_tm_voting_power(params.tm_votes_per_token, stake2), 0); assert_eq!(into_tm_voting_power(params.tm_votes_per_token, stake3), 0); - update_validator_set( - &mut s, - ¶ms, - &val2, - bond2.change(), - pipeline_epoch, - ) - .unwrap(); + update_validator_set(&mut s, ¶ms, &val2, bond2.change(), epoch, None) + .unwrap(); update_validator_deltas( &mut s, + ¶ms, &val2, bond2.change(), epoch, - params.pipeline_len, + None, ) .unwrap(); - update_validator_set( - &mut s, - ¶ms, - &val3, - bond3.change(), - pipeline_epoch, - ) - .unwrap(); + update_validator_set(&mut s, ¶ms, &val3, bond3.change(), epoch, None) + .unwrap(); update_validator_deltas( &mut s, + ¶ms, &val3, bond3.change(), epoch, - params.pipeline_len, + None, ) .unwrap(); @@ -2084,37 +2055,27 @@ fn test_validator_sets_swap() { into_tm_voting_power(params.tm_votes_per_token, stake3) ); - update_validator_set( - &mut s, - ¶ms, - &val2, - bonds.change(), - pipeline_epoch, - ) - .unwrap(); + update_validator_set(&mut s, ¶ms, &val2, bonds.change(), epoch, None) + .unwrap(); update_validator_deltas( &mut s, + ¶ms, &val2, bonds.change(), epoch, - params.pipeline_len, + None, ) .unwrap(); - update_validator_set( - &mut s, - ¶ms, - &val3, - bonds.change(), - pipeline_epoch, - ) - .unwrap(); + update_validator_set(&mut s, ¶ms, &val3, bonds.change(), epoch, None) + .unwrap(); update_validator_deltas( &mut s, + ¶ms, &val3, bonds.change(), epoch, - params.pipeline_len, + None, ) .unwrap(); @@ -4373,6 +4334,7 @@ fn test_simple_redelegation_aux( &src_validator, amount_delegate, current_epoch, + None, ) .unwrap(); @@ -4768,6 +4730,7 @@ fn test_redelegation_with_slashing_aux( &src_validator, amount_delegate, current_epoch, + None, ) .unwrap(); @@ -5163,6 +5126,7 @@ fn test_chain_redelegations_aux(mut validators: Vec) { &src_validator, bond_amount, current_epoch, + None, ) .unwrap(); @@ -5643,6 +5607,7 @@ fn test_overslashing_aux(mut validators: Vec) { &validator, amount_del, current_epoch, + None, ) .unwrap(); diff --git a/proof_of_stake/src/tests/state_machine.rs b/proof_of_stake/src/tests/state_machine.rs index a067037232..21be643af6 100644 --- a/proof_of_stake/src/tests/state_machine.rs +++ b/proof_of_stake/src/tests/state_machine.rs @@ -217,7 +217,7 @@ impl StateMachineTest for ConcretePosState { .collect::>() ); let mut s = TestWlStorage::default(); - crate::test_init_genesis( + crate::test_utils::test_init_genesis( &mut s, initial_state.params.owned.clone(), initial_state.genesis_validators.clone().into_iter(), @@ -275,6 +275,7 @@ impl StateMachineTest for ConcretePosState { current_epoch, commission_rate, max_commission_rate_change, + offset_opt: None, }) .unwrap(); @@ -338,6 +339,7 @@ impl StateMachineTest for ConcretePosState { &id.validator, amount, current_epoch, + None, ) .unwrap(); diff --git a/proof_of_stake/src/tests/state_machine_v2.rs b/proof_of_stake/src/tests/state_machine_v2.rs index 02df1b39a0..81f8226ac9 100644 --- a/proof_of_stake/src/tests/state_machine_v2.rs +++ b/proof_of_stake/src/tests/state_machine_v2.rs @@ -1929,7 +1929,7 @@ impl StateMachineTest for ConcretePosState { .collect::>() ); let mut s = TestWlStorage::default(); - crate::init_genesis( + crate::test_utils::init_genesis_helper( &mut s, &initial_state.params, initial_state.genesis_validators.clone().into_iter(), @@ -2003,6 +2003,7 @@ impl StateMachineTest for ConcretePosState { current_epoch, commission_rate, max_commission_rate_change, + offset_opt: None, }) .unwrap(); @@ -2066,6 +2067,7 @@ impl StateMachineTest for ConcretePosState { &id.validator, amount, current_epoch, + None, ) .unwrap(); diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index f9df559c8a..7162f92772 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -118,5 +118,6 @@ wasmtimer = "0.2.0" [dev-dependencies] assert_matches.workspace = true +base58.workspace = true namada_test_utils = {path = "../test_utils"} tempfile.workspace = true diff --git a/sdk/src/args.rs b/sdk/src/args.rs index f9b02ebafb..f8454648b5 100644 --- a/sdk/src/args.rs +++ b/sdk/src/args.rs @@ -1695,6 +1695,9 @@ pub struct MaspAddrKeyAdd { pub alias_force: bool, /// Any MASP value pub value: MaspValue, + /// Add a MASP key / address pre-genesis instead + /// of a current chain + pub is_pre_genesis: bool, /// Don't encrypt the keypair pub unsafe_dont_encrypt: bool, } @@ -1706,6 +1709,8 @@ pub struct MaspSpendKeyGen { pub alias: String, /// Whether to force overwrite the alias pub alias_force: bool, + /// Generate spending key pre-genesis instead of a current chain + pub is_pre_genesis: bool, /// Don't encrypt the keypair pub unsafe_dont_encrypt: bool, } @@ -1721,6 +1726,8 @@ pub struct MaspPayAddrGen { pub viewing_key: C::ViewingKey, /// Pin pub pin: bool, + /// Generate an address pre-genesis instead of a current chain + pub is_pre_genesis: bool, } /// Wallet generate key and implicit address arguments @@ -1732,6 +1739,8 @@ pub struct KeyAndAddressGen { pub alias: Option, /// Whether to force overwrite the alias, if provided pub alias_force: bool, + /// Generate a key for pre-genesis, instead of a current chain + pub is_pre_genesis: bool, /// Don't encrypt the keypair pub unsafe_dont_encrypt: bool, /// BIP44 derivation path @@ -1762,6 +1771,8 @@ pub struct KeyFind { pub alias: Option, /// Public key hash to lookup keypair with pub value: Option, + /// Find a key pre-genesis instead of a current chain + pub is_pre_genesis: bool, /// Show secret keys to user pub unsafe_show_secret: bool, } @@ -1773,6 +1784,8 @@ pub struct AddrKeyFind { pub alias: String, /// Show secret keys to user pub unsafe_show_secret: bool, + /// Find shielded address / key pre-genesis instead of a current chain + pub is_pre_genesis: bool, } /// Wallet list shielded keys arguments @@ -1780,15 +1793,27 @@ pub struct AddrKeyFind { pub struct MaspKeysList { /// Don't decrypt spending keys pub decrypt: bool, + /// List shielded keys pre-genesis instead of a current chain + pub is_pre_genesis: bool, /// Show secret keys to user pub unsafe_show_secret: bool, } +/// Wallet list shielded payment addresses arguments +#[derive(Clone, Debug)] +pub struct MaspListPayAddrs { + /// List sheilded payment address pre-genesis instead + /// of a current chain + pub is_pre_genesis: bool, +} + /// Wallet list keys arguments #[derive(Clone, Debug)] pub struct KeyList { /// Don't decrypt keypairs pub decrypt: bool, + /// List keys pre-genesis instead of a current chain + pub is_pre_genesis: bool, /// Show secret keys to user pub unsafe_show_secret: bool, } @@ -1798,6 +1823,8 @@ pub struct KeyList { pub struct KeyExport { /// Key alias pub alias: String, + /// Export key pre-genesis instead of a current chain + pub is_pre_genesis: bool, } /// Wallet address lookup arguments @@ -1807,6 +1834,15 @@ pub struct AddressOrAliasFind { pub alias: Option, /// Address to find pub address: Option
, + /// Lookup address pre-genesis instead of a current chain + pub is_pre_genesis: bool, +} + +/// List wallet address +#[derive(Clone, Debug)] +pub struct AddressList { + /// List addresses pre-genesis instead of current chain + pub is_pre_genesis: bool, } /// Wallet address add arguments @@ -1818,6 +1854,8 @@ pub struct AddressAdd { pub alias_force: bool, /// Address to add pub address: Address, + /// Add an address pre-genesis instead of current chain + pub is_pre_genesis: bool, } /// Bridge pool batch recommendation. diff --git a/sdk/src/error.rs b/sdk/src/error.rs index 9f84195cc2..3512d1a9f2 100644 --- a/sdk/src/error.rs +++ b/sdk/src/error.rs @@ -19,7 +19,7 @@ pub type Result = std::result::Result; /// /// The general mentality should be that this error type should cover all /// possible errors that one may face. -#[derive(Clone, Error, Debug)] +#[derive(Error, Debug)] pub enum Error { /// Errors that are caused by trying to retrieve a pinned transaction #[error("Error in retrieving pinned balance: {0}")] diff --git a/sdk/src/wallet/alias.rs b/sdk/src/wallet/alias.rs index 13d977b852..48ab4a9fa0 100644 --- a/sdk/src/wallet/alias.rs +++ b/sdk/src/wallet/alias.rs @@ -3,15 +3,17 @@ use std::convert::Infallible; use std::fmt::Display; use std::hash::Hash; +use std::io::Read; use std::str::FromStr; +use borsh::{BorshDeserialize, BorshSerialize}; +use namada_core::types::address::{Address, InternalAddress}; use serde::{Deserialize, Serialize}; /// Aliases created from raw strings are kept in-memory as given, but their /// `Serialize` and `Display` instance converts them to lowercase. Their /// `PartialEq` instance is case-insensitive. -#[derive(Clone, Debug, Default, Deserialize, PartialOrd, Ord, Eq)] -#[serde(transparent)] +#[derive(Clone, Debug, Default, Eq)] pub struct Alias(String); impl Alias { @@ -29,6 +31,33 @@ impl Alias { pub fn is_empty(&self) -> bool { self.0.is_empty() } + + /// If the alias is reserved for an internal address, + /// return that address + pub fn is_reserved(alias: impl AsRef) -> Option
{ + InternalAddress::try_from_alias(alias.as_ref()).map(Address::Internal) + } +} + +impl BorshSerialize for Alias { + fn serialize( + &self, + writer: &mut W, + ) -> std::io::Result<()> { + BorshSerialize::serialize(&self.normalize(), writer) + } +} + +impl BorshDeserialize for Alias { + fn deserialize(buf: &mut &[u8]) -> std::io::Result { + let raw: String = BorshDeserialize::deserialize(buf)?; + Ok(Self::from(raw)) + } + + fn deserialize_reader(reader: &mut R) -> std::io::Result { + let raw: String = BorshDeserialize::deserialize_reader(reader)?; + Ok(Self::from(raw)) + } } impl Serialize for Alias { @@ -36,7 +65,17 @@ impl Serialize for Alias { where S: serde::Serializer, { - self.normalize().serialize(serializer) + Serialize::serialize(&self.normalize(), serializer) + } +} + +impl<'de> Deserialize<'de> for Alias { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let raw: String = Deserialize::deserialize(deserializer)?; + Ok(Self::from(raw)) } } @@ -46,6 +85,18 @@ impl PartialEq for Alias { } } +impl PartialOrd for Alias { + fn partial_cmp(&self, other: &Self) -> Option { + self.normalize().partial_cmp(&other.normalize()) + } +} + +impl Ord for Alias { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.normalize().cmp(&other.normalize()) + } +} + impl Hash for Alias { fn hash(&self, state: &mut H) { self.normalize().hash(state); @@ -57,7 +108,7 @@ where T: AsRef, { fn from(raw: T) -> Self { - Self(raw.as_ref().to_owned()) + Self(raw.as_ref().to_lowercase()) } } @@ -83,7 +134,13 @@ impl FromStr for Alias { type Err = Infallible; fn from_str(s: &str) -> Result { - Ok(Self(s.into())) + Ok(Self::from(s)) + } +} + +impl AsRef for &Alias { + fn as_ref(&self) -> &str { + &self.0 } } diff --git a/sdk/src/wallet/mod.rs b/sdk/src/wallet/mod.rs index 67d5a11f86..4570c8a283 100644 --- a/sdk/src/wallet/mod.rs +++ b/sdk/src/wallet/mod.rs @@ -346,8 +346,17 @@ impl Wallet { } /// Find the stored address by an alias. - pub fn find_address(&self, alias: impl AsRef) -> Option<&Address> { - self.store.find_address(alias) + pub fn find_address( + &self, + alias: impl AsRef, + ) -> Option> { + Alias::is_reserved(alias.as_ref()) + .map(std::borrow::Cow::Owned) + .or_else(|| { + self.store + .find_address(alias) + .map(std::borrow::Cow::Borrowed) + }) } /// Find an alias by the address if it's in the wallet. @@ -883,4 +892,10 @@ impl Wallet { .insert_payment_addr::(alias.into(), payment_addr, force_alias) .map(Into::into) } + + /// Extend this wallet from another wallet (typically pre-genesis). + /// Note that this method ignores `store.validator_data` if any. + pub fn extend(&mut self, wallet: Self) { + self.store.extend(wallet.store) + } } diff --git a/sdk/src/wallet/store.rs b/sdk/src/wallet/store.rs index 201cc885a4..4b13291e03 100644 --- a/sdk/src/wallet/store.rs +++ b/sdk/src/wallet/store.rs @@ -1,10 +1,10 @@ //! Wallet Store information -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeMap, HashSet}; use std::fmt::Display; use std::str::FromStr; -use bimap::BiHashMap; +use bimap::BiBTreeMap; use bip39::Seed; use itertools::Itertools; use masp_primitives::zip32::ExtendedFullViewingKey; @@ -67,26 +67,26 @@ pub struct ValidatorData { #[derive(Serialize, Deserialize, Debug, Default)] pub struct Store { /// Known viewing keys - view_keys: HashMap, + view_keys: BTreeMap, /// Known spending keys - spend_keys: HashMap>, + spend_keys: BTreeMap>, /// Known payment addresses - payment_addrs: HashMap, + payment_addrs: BTreeMap, /// Cryptographic keypairs - keys: HashMap>, + keys: BTreeMap>, /// Namada address book - addresses: BiHashMap, + addresses: BiBTreeMap, /// Known mappings of public key hashes to their aliases in the `keys` /// field. Used for look-up by a public key. - pkhs: HashMap, + pkhs: BTreeMap, /// Special keys if the wallet belongs to a validator pub(crate) validator_data: Option, /// Namada address vp type - address_vp_types: HashMap>, + address_vp_types: BTreeMap>, } /// Grouping of addresses by validity predicate. -#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, PartialOrd, Ord)] pub enum AddressVpType { /// The Token Token, @@ -174,11 +174,11 @@ impl Store { /// Get all known keys by their alias, paired with PKH, if known. pub fn get_keys( &self, - ) -> HashMap< + ) -> BTreeMap< Alias, (&StoredKeypair, Option<&PublicKeyHash>), > { - let mut keys: HashMap< + let mut keys: BTreeMap< Alias, (&StoredKeypair, Option<&PublicKeyHash>), > = self @@ -198,24 +198,24 @@ impl Store { } /// Get all known addresses by their alias. - pub fn get_addresses(&self) -> &BiHashMap { + pub fn get_addresses(&self) -> &BiBTreeMap { &self.addresses } /// Get all known payment addresses by their alias. - pub fn get_payment_addrs(&self) -> &HashMap { + pub fn get_payment_addrs(&self) -> &BTreeMap { &self.payment_addrs } /// Get all known viewing keys by their alias. - pub fn get_viewing_keys(&self) -> &HashMap { + pub fn get_viewing_keys(&self) -> &BTreeMap { &self.view_keys } /// Get all known spending keys by their alias. pub fn get_spending_keys( &self, - ) -> &HashMap> { + ) -> &BTreeMap> { &self.spend_keys } @@ -247,6 +247,10 @@ impl Store { seed_and_derivation_path: Option<(Seed, DerivationPath)>, password: Option>, ) -> Option<(Alias, common::SecretKey)> { + // We cannot generate keys for reserved aliases + if alias.as_ref().and_then(Alias::is_reserved).is_some() { + return None; + } let sk = if let Some((seed, derivation_path)) = seed_and_derivation_path { gen_sk_from_seed_and_derivation_path( @@ -283,6 +287,13 @@ impl Store { password: Option>, force_alias: bool, ) -> (Alias, ExtendedSpendingKey) { + if Alias::is_reserved(&alias).is_some() { + panic!( + "Tried to generated spending key with reserved alias: {}. \ + Action cancelled, no changes persisted.", + alias + ); + } let spendkey = Self::generate_spending_key(); let viewkey = ExtendedFullViewingKey::from(&spendkey.into()).into(); let (spendkey_to_store, _raw_spendkey) = @@ -348,6 +359,12 @@ impl Store { return None; } + // abort if the alias is reserved + if Alias::is_reserved(&alias).is_some() { + println!("The alias {} is reserved", alias); + return None; + } + if alias.is_empty() { println!( "Empty alias given, defaulting to {}.", @@ -395,6 +412,12 @@ impl Store { viewkey: ExtendedViewingKey, force: bool, ) -> Option { + // abort if the alias is reserved + if Alias::is_reserved(&alias).is_some() { + println!("The alias {} is reserved", alias); + return None; + } + if alias.is_empty() { eprintln!("Empty alias given."); return None; @@ -424,6 +447,12 @@ impl Store { viewkey: ExtendedViewingKey, force: bool, ) -> Option { + // abort if the alias is reserved + if Alias::is_reserved(&alias).is_some() { + println!("The alias {} is reserved", alias); + return None; + } + if alias.is_empty() { eprintln!("Empty alias given."); return None; @@ -469,6 +498,12 @@ impl Store { payment_addr: PaymentAddress, force: bool, ) -> Option { + // abort if the alias is reserved + if Alias::is_reserved(&alias).is_some() { + println!("The alias {} is reserved", alias); + return None; + } + if alias.is_empty() { eprintln!("Empty alias given."); return None; @@ -499,6 +534,11 @@ impl Store { key: Option>, pkh: Option, ) { + // abort if the alias is reserved + if Alias::is_reserved(&alias).is_some() { + println!("The alias {} is reserved", alias); + return; + } key.map(|x| self.keys.insert(alias.clone(), x)); pkh.map(|x| self.pkhs.insert(x, alias.clone())); } @@ -513,6 +553,11 @@ impl Store { address: Address, force: bool, ) -> Option { + // abort if the alias is reserved + if Alias::is_reserved(&alias).is_some() { + println!("The alias {} is reserved", alias); + return None; + } // abort if the address already exists in the wallet if self.addresses.contains_right(&address) && !force { println!( @@ -571,6 +616,28 @@ impl Store { Some(alias) } + /// Extend this store from another store (typically pre-genesis). + /// Note that this method ignores `validator_data` if any. + pub fn extend(&mut self, store: Store) { + let Self { + view_keys, + spend_keys, + payment_addrs, + keys, + addresses, + pkhs, + validator_data: _, + address_vp_types, + } = self; + view_keys.extend(store.view_keys); + spend_keys.extend(store.spend_keys); + payment_addrs.extend(store.payment_addrs); + keys.extend(store.keys); + addresses.extend(store.addresses); + pkhs.extend(store.pkhs); + address_vp_types.extend(store.address_vp_types); + } + /// Extend this store from pre-genesis validator wallet. pub fn extend_from_pre_genesis_validator( &mut self, @@ -750,7 +817,7 @@ impl<'de> Deserialize<'de> for AddressVpType { } } -#[cfg(all(test, feature = "dev"))] +#[cfg(test)] mod test_wallet { use base58::{self, FromBase58}; use bip39::{Language, Mnemonic}; diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 648f7e0b39..ce39a22ef8 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -20,8 +20,6 @@ mainnet = [ "namada_core/mainnet", ] std = ["fd-lock"] -# NOTE "dev" features that shouldn't be used in live networks are enabled by default for now -dev = [] ferveo-tpke = [ "namada_core/ferveo-tpke", "namada_sdk/ferveo-tpke", @@ -169,6 +167,7 @@ wasmtimer = "0.2.0" [dev-dependencies] namada_core = {path = "../core", default-features = false, features = ["testing", "ibc-mocks"]} namada_ethereum_bridge = {path = "../ethereum_bridge", default-features = false, features = ["testing"]} +namada_proof_of_stake = {path = "../proof_of_stake", default-features = false, features = ["testing"]} namada_test_utils = {path = "../test_utils"} assert_matches.workspace = true async-trait.workspace = true diff --git a/shared/build.rs b/shared/build.rs deleted file mode 100644 index b97290123c..0000000000 --- a/shared/build.rs +++ /dev/null @@ -1,12 +0,0 @@ -use std::env; - -fn main() { - // Tell Cargo to build when the `NAMADA_DEV` env var changes - println!("cargo:rerun-if-env-changed=NAMADA_DEV"); - // Enable "dev" feature if `NAMADA_DEV` is trueish - if let Ok(dev) = env::var("NAMADA_DEV") { - if dev.to_ascii_lowercase().trim() == "true" { - println!("cargo:rustc-cfg=feature=\"dev\""); - } - } -} diff --git a/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs b/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs index 6d07f5a613..01671ea882 100644 --- a/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs @@ -647,7 +647,7 @@ mod test_bridge_pool_vp { use namada_core::ledger::gas::TxGasMeter; use namada_core::types::address; use namada_ethereum_bridge::parameters::{ - Contracts, EthereumBridgeConfig, UpgradeableContract, + Contracts, EthereumBridgeParams, UpgradeableContract, }; use super::*; @@ -901,7 +901,7 @@ mod test_bridge_pool_vp { /// Initialize some dummy storage for testing fn setup_storage() -> WlStorage { // a dummy config for testing - let config = EthereumBridgeConfig { + let config = EthereumBridgeParams { erc20_whitelist: vec![], eth_start_height: Default::default(), min_confirmations: Default::default(), diff --git a/shared/src/ledger/native_vp/ethereum_bridge/vp.rs b/shared/src/ledger/native_vp/ethereum_bridge/vp.rs index 9f5f6dd19c..88c1f07044 100644 --- a/shared/src/ledger/native_vp/ethereum_bridge/vp.rs +++ b/shared/src/ledger/native_vp/ethereum_bridge/vp.rs @@ -171,7 +171,7 @@ mod tests { use namada_core::ledger::gas::TxGasMeter; use namada_core::ledger::storage_api::StorageWrite; use namada_ethereum_bridge::parameters::{ - Contracts, EthereumBridgeConfig, UpgradeableContract, + Contracts, EthereumBridgeParams, UpgradeableContract, }; use rand::Rng; @@ -225,7 +225,7 @@ mod tests { .expect("Test failed"); // a dummy config for testing - let config = EthereumBridgeConfig { + let config = EthereumBridgeParams { erc20_whitelist: vec![], eth_start_height: Default::default(), min_confirmations: Default::default(), diff --git a/shared/src/ledger/native_vp/ibc/mod.rs b/shared/src/ledger/native_vp/ibc/mod.rs index b0901f6fec..902bef0a5b 100644 --- a/shared/src/ledger/native_vp/ibc/mod.rs +++ b/shared/src/ledger/native_vp/ibc/mod.rs @@ -389,7 +389,6 @@ mod tests { }; use crate::ledger::parameters::EpochDuration; use crate::ledger::{ibc, pos}; - use crate::proof_of_stake::parameters::PosParams; use crate::proto::{Code, Data, Section, Signature, Tx}; use crate::tendermint::time::Time as TmTime; use crate::tendermint_proto::Protobuf as TmProtobuf; @@ -416,12 +415,13 @@ mod tests { ibc::init_genesis_storage(&mut wl_storage); let gov_params = GovernanceParameters::default(); gov_params.init_storage(&mut wl_storage).unwrap(); - pos::init_genesis_storage( + pos::test_utils::test_init_genesis( &mut wl_storage, - &PosParams::default(), + namada_proof_of_stake::OwnedPosParams::default(), vec![get_dummy_genesis_validator()].into_iter(), Epoch(1), - ); + ) + .unwrap(); // epoch duration let epoch_duration_key = get_epoch_duration_storage_key(); let epoch_duration = EpochDuration { diff --git a/shared/src/ledger/pos/mod.rs b/shared/src/ledger/pos/mod.rs index d47e5cc884..620d0108d3 100644 --- a/shared/src/ledger/pos/mod.rs +++ b/shared/src/ledger/pos/mod.rs @@ -5,7 +5,6 @@ pub mod vp; use std::convert::TryFrom; pub use namada_core::ledger::storage_api; -use namada_core::ledger::storage_api::{StorageRead, StorageWrite}; use namada_core::types::address; pub use namada_core::types::dec::Dec; pub use namada_core::types::key::common; @@ -14,11 +13,12 @@ pub use namada_proof_of_stake; pub use namada_proof_of_stake::parameters::{OwnedPosParams, PosParams}; pub use namada_proof_of_stake::pos_queries::*; pub use namada_proof_of_stake::storage::*; +#[cfg(any(test, feature = "testing"))] +pub use namada_proof_of_stake::test_utils; pub use namada_proof_of_stake::{staking_token_address, types}; pub use vp::PosVP; use crate::types::address::{Address, InternalAddress}; -use crate::types::storage::Epoch; /// Address of the PoS account implemented as a native VP pub const ADDRESS: Address = address::POS; @@ -39,24 +39,6 @@ pub fn into_tm_voting_power( i64::try_from(res).expect("Invalid validator voting power (i64)") } -/// Initialize storage in the genesis block. -pub fn init_genesis_storage( - storage: &mut S, - params: &OwnedPosParams, - validators: impl Iterator + Clone, - current_epoch: Epoch, -) where - S: StorageRead + StorageWrite, -{ - namada_proof_of_stake::init_genesis( - storage, - params, - validators, - current_epoch, - ) - .expect("Initialize PoS genesis storage"); -} - /// Alias for a PoS type with the same name with concrete type parameters pub type BondId = namada_proof_of_stake::types::BondId; diff --git a/shared/src/ledger/vp_host_fns.rs b/shared/src/ledger/vp_host_fns.rs index ebe32d4f72..ef8831ae88 100644 --- a/shared/src/ledger/vp_host_fns.rs +++ b/shared/src/ledger/vp_host_fns.rs @@ -321,7 +321,7 @@ pub fn get_tx_index( Ok(*tx_index) } -/// Getting the chain ID. +/// Getting the native token's address. pub fn get_native_token( gas_meter: &mut VpGasMeter, storage: &Storage, diff --git a/test_fixtures/masp_proofs/02710BEDC9AAEF8281AD3294C0CEA6651CDA5F544434A2FB9C5743209C0FE00E.bin b/test_fixtures/masp_proofs/02710BEDC9AAEF8281AD3294C0CEA6651CDA5F544434A2FB9C5743209C0FE00E.bin new file mode 100644 index 0000000000..c6bbdf46ec Binary files /dev/null and b/test_fixtures/masp_proofs/02710BEDC9AAEF8281AD3294C0CEA6651CDA5F544434A2FB9C5743209C0FE00E.bin differ diff --git a/test_fixtures/masp_proofs/1701259D7EE079DAD0D27D1CDFCE2366CA1ECCFB3B50AADB75C4369AA9B1FE2E.bin b/test_fixtures/masp_proofs/1701259D7EE079DAD0D27D1CDFCE2366CA1ECCFB3B50AADB75C4369AA9B1FE2E.bin new file mode 100644 index 0000000000..ec194bc8a7 Binary files /dev/null and b/test_fixtures/masp_proofs/1701259D7EE079DAD0D27D1CDFCE2366CA1ECCFB3B50AADB75C4369AA9B1FE2E.bin differ diff --git a/test_fixtures/masp_proofs/28DCE740F1BD7D7206389056C49AD1132C9C91D103BB2A0C3F0FD677B1E3B735.bin b/test_fixtures/masp_proofs/28DCE740F1BD7D7206389056C49AD1132C9C91D103BB2A0C3F0FD677B1E3B735.bin new file mode 100644 index 0000000000..f25848165a Binary files /dev/null and b/test_fixtures/masp_proofs/28DCE740F1BD7D7206389056C49AD1132C9C91D103BB2A0C3F0FD677B1E3B735.bin differ diff --git a/test_fixtures/masp_proofs/29AC8DE3B07495BEABEAF50FE8FF8BF07596031CD460FEBFAEA4F75AF65D5402.bin b/test_fixtures/masp_proofs/29AC8DE3B07495BEABEAF50FE8FF8BF07596031CD460FEBFAEA4F75AF65D5402.bin deleted file mode 100644 index b748454de8..0000000000 Binary files a/test_fixtures/masp_proofs/29AC8DE3B07495BEABEAF50FE8FF8BF07596031CD460FEBFAEA4F75AF65D5402.bin and /dev/null differ diff --git a/test_fixtures/masp_proofs/37332141CB34FC30FF51F4BEE8D76149D3088F539CF8372D404609B89B095EF7.bin b/test_fixtures/masp_proofs/37332141CB34FC30FF51F4BEE8D76149D3088F539CF8372D404609B89B095EF7.bin deleted file mode 100644 index 18f83d0543..0000000000 Binary files a/test_fixtures/masp_proofs/37332141CB34FC30FF51F4BEE8D76149D3088F539CF8372D404609B89B095EF7.bin and /dev/null differ diff --git a/test_fixtures/masp_proofs/3BE539A4BD341C5D970735AB70AB97FD31CF24CDED81325016A0657158104757.bin b/test_fixtures/masp_proofs/3BE539A4BD341C5D970735AB70AB97FD31CF24CDED81325016A0657158104757.bin new file mode 100644 index 0000000000..8de16fa778 Binary files /dev/null and b/test_fixtures/masp_proofs/3BE539A4BD341C5D970735AB70AB97FD31CF24CDED81325016A0657158104757.bin differ diff --git a/test_fixtures/masp_proofs/3CE0EAA759CE865C5F92F4739E024A7C32F1FD5A19811F4616E7E03C4B1DB10C.bin b/test_fixtures/masp_proofs/3CE0EAA759CE865C5F92F4739E024A7C32F1FD5A19811F4616E7E03C4B1DB10C.bin new file mode 100644 index 0000000000..dd35f13453 Binary files /dev/null and b/test_fixtures/masp_proofs/3CE0EAA759CE865C5F92F4739E024A7C32F1FD5A19811F4616E7E03C4B1DB10C.bin differ diff --git a/test_fixtures/masp_proofs/52984E26D4A044A259B441C1DAEB66F886B3A3B6C71D33F456B859D01DA47ADD.bin b/test_fixtures/masp_proofs/52984E26D4A044A259B441C1DAEB66F886B3A3B6C71D33F456B859D01DA47ADD.bin deleted file mode 100644 index c51b5ed0d9..0000000000 Binary files a/test_fixtures/masp_proofs/52984E26D4A044A259B441C1DAEB66F886B3A3B6C71D33F456B859D01DA47ADD.bin and /dev/null differ diff --git a/test_fixtures/masp_proofs/71630F282F9492A759691BEBC4F036695988B745CBF51F6D8CB56C6A11C9D498.bin b/test_fixtures/masp_proofs/71630F282F9492A759691BEBC4F036695988B745CBF51F6D8CB56C6A11C9D498.bin new file mode 100644 index 0000000000..e44a42c0d2 Binary files /dev/null and b/test_fixtures/masp_proofs/71630F282F9492A759691BEBC4F036695988B745CBF51F6D8CB56C6A11C9D498.bin differ diff --git a/test_fixtures/masp_proofs/823B8C4280FBB0A1CF75A4EE81B3E0126D872DE64654C9F7225AE18E42650564.bin b/test_fixtures/masp_proofs/823B8C4280FBB0A1CF75A4EE81B3E0126D872DE64654C9F7225AE18E42650564.bin new file mode 100644 index 0000000000..d6149d921d Binary files /dev/null and b/test_fixtures/masp_proofs/823B8C4280FBB0A1CF75A4EE81B3E0126D872DE64654C9F7225AE18E42650564.bin differ diff --git a/test_fixtures/masp_proofs/9883C2EF7971504BB1CF651BAFFC3DA2C57E4FD8FF0811D9CB129887F0F9F706.bin b/test_fixtures/masp_proofs/9883C2EF7971504BB1CF651BAFFC3DA2C57E4FD8FF0811D9CB129887F0F9F706.bin deleted file mode 100644 index 094103100a..0000000000 Binary files a/test_fixtures/masp_proofs/9883C2EF7971504BB1CF651BAFFC3DA2C57E4FD8FF0811D9CB129887F0F9F706.bin and /dev/null differ diff --git a/test_fixtures/masp_proofs/99393E3AC8046F86ABA05519568B6780B6F18A312AE3909BEA19D16FCFE837DC.bin b/test_fixtures/masp_proofs/99393E3AC8046F86ABA05519568B6780B6F18A312AE3909BEA19D16FCFE837DC.bin deleted file mode 100644 index f456d94d7d..0000000000 Binary files a/test_fixtures/masp_proofs/99393E3AC8046F86ABA05519568B6780B6F18A312AE3909BEA19D16FCFE837DC.bin and /dev/null differ diff --git a/test_fixtures/masp_proofs/9CCDEA31EDE3973806C1247BD7036C47707BFCE3CE7672A526ADEFA192F21577.bin b/test_fixtures/masp_proofs/9CCDEA31EDE3973806C1247BD7036C47707BFCE3CE7672A526ADEFA192F21577.bin new file mode 100644 index 0000000000..521a1e1431 Binary files /dev/null and b/test_fixtures/masp_proofs/9CCDEA31EDE3973806C1247BD7036C47707BFCE3CE7672A526ADEFA192F21577.bin differ diff --git a/test_fixtures/masp_proofs/9D2160E776A448BE5E380DEE784DEE977AB0FC421801231D05A1384ED6185D90.bin b/test_fixtures/masp_proofs/9D2160E776A448BE5E380DEE784DEE977AB0FC421801231D05A1384ED6185D90.bin new file mode 100644 index 0000000000..86bd6b2d1f Binary files /dev/null and b/test_fixtures/masp_proofs/9D2160E776A448BE5E380DEE784DEE977AB0FC421801231D05A1384ED6185D90.bin differ diff --git a/test_fixtures/masp_proofs/1362F1CF9B836CF8B05D8189EA9CB1712CCA85B0E96A3330A63BE7CD9E5ECD22.bin b/test_fixtures/masp_proofs/AAF849D0C3C7352A4241AB8C9918ED96DFBF9A925BDE8E2A32DD3A3A160E74D9.bin similarity index 54% rename from test_fixtures/masp_proofs/1362F1CF9B836CF8B05D8189EA9CB1712CCA85B0E96A3330A63BE7CD9E5ECD22.bin rename to test_fixtures/masp_proofs/AAF849D0C3C7352A4241AB8C9918ED96DFBF9A925BDE8E2A32DD3A3A160E74D9.bin index 741f4d5106..1ee637c62d 100644 Binary files a/test_fixtures/masp_proofs/1362F1CF9B836CF8B05D8189EA9CB1712CCA85B0E96A3330A63BE7CD9E5ECD22.bin and b/test_fixtures/masp_proofs/AAF849D0C3C7352A4241AB8C9918ED96DFBF9A925BDE8E2A32DD3A3A160E74D9.bin differ diff --git a/test_fixtures/masp_proofs/BA4FED83467B6FEE522748C6F7E72A01F0B169F946835583DC2C71B550315603.bin b/test_fixtures/masp_proofs/BA4FED83467B6FEE522748C6F7E72A01F0B169F946835583DC2C71B550315603.bin deleted file mode 100644 index 565d189c0c..0000000000 Binary files a/test_fixtures/masp_proofs/BA4FED83467B6FEE522748C6F7E72A01F0B169F946835583DC2C71B550315603.bin and /dev/null differ diff --git a/test_fixtures/masp_proofs/C7ECE8C02C2E764EFD5B6A0756CFE8EEC08E2D8512695A667D294AE1A4A8D4E6.bin b/test_fixtures/masp_proofs/C7ECE8C02C2E764EFD5B6A0756CFE8EEC08E2D8512695A667D294AE1A4A8D4E6.bin deleted file mode 100644 index d7fe00a74f..0000000000 Binary files a/test_fixtures/masp_proofs/C7ECE8C02C2E764EFD5B6A0756CFE8EEC08E2D8512695A667D294AE1A4A8D4E6.bin and /dev/null differ diff --git a/test_fixtures/masp_proofs/F36A8353F15FD6D8158DBC67DDB827EEEDA858AB983D16024AAA415579A68953.bin b/test_fixtures/masp_proofs/D58CB9F05F35F3001E8C3026F8CA923BE35ED8C131F062F63AC08645A8C82CFC.bin similarity index 50% rename from test_fixtures/masp_proofs/F36A8353F15FD6D8158DBC67DDB827EEEDA858AB983D16024AAA415579A68953.bin rename to test_fixtures/masp_proofs/D58CB9F05F35F3001E8C3026F8CA923BE35ED8C131F062F63AC08645A8C82CFC.bin index 755c32011e..98a7b4f00c 100644 Binary files a/test_fixtures/masp_proofs/F36A8353F15FD6D8158DBC67DDB827EEEDA858AB983D16024AAA415579A68953.bin and b/test_fixtures/masp_proofs/D58CB9F05F35F3001E8C3026F8CA923BE35ED8C131F062F63AC08645A8C82CFC.bin differ diff --git a/test_fixtures/masp_proofs/EE7C912B7E21F07494D58AA6668DC6BBB31619C7E93A1A5A2E64B694DBE1BD6E.bin b/test_fixtures/masp_proofs/EE7C912B7E21F07494D58AA6668DC6BBB31619C7E93A1A5A2E64B694DBE1BD6E.bin deleted file mode 100644 index fee4361a2b..0000000000 Binary files a/test_fixtures/masp_proofs/EE7C912B7E21F07494D58AA6668DC6BBB31619C7E93A1A5A2E64B694DBE1BD6E.bin and /dev/null differ diff --git a/test_fixtures/masp_proofs/EEB91EB873807EC77BBCA95D4CFA3F379DB351AB4AE081207ABFDFC429C9FA48.bin b/test_fixtures/masp_proofs/EEB91EB873807EC77BBCA95D4CFA3F379DB351AB4AE081207ABFDFC429C9FA48.bin deleted file mode 100644 index 5064fd5593..0000000000 Binary files a/test_fixtures/masp_proofs/EEB91EB873807EC77BBCA95D4CFA3F379DB351AB4AE081207ABFDFC429C9FA48.bin and /dev/null differ diff --git a/test_fixtures/masp_proofs/F068FDF05B8F25DD923E667215344FFFAA6CA273027CD480AEA68DDED57D88CA.bin b/test_fixtures/masp_proofs/F068FDF05B8F25DD923E667215344FFFAA6CA273027CD480AEA68DDED57D88CA.bin deleted file mode 100644 index 3b05c546b0..0000000000 Binary files a/test_fixtures/masp_proofs/F068FDF05B8F25DD923E667215344FFFAA6CA273027CD480AEA68DDED57D88CA.bin and /dev/null differ diff --git a/test_fixtures/masp_proofs/8B29BC2E1A96DF331C7C3A2B227C98D1E5AAAA9988F26B1A47090ACCE693572F.bin b/test_fixtures/masp_proofs/F534964AD0DF72DF15E18A0FE7629B8C97C137CD8F44F86196F2C42FE45201D9.bin similarity index 51% rename from test_fixtures/masp_proofs/8B29BC2E1A96DF331C7C3A2B227C98D1E5AAAA9988F26B1A47090ACCE693572F.bin rename to test_fixtures/masp_proofs/F534964AD0DF72DF15E18A0FE7629B8C97C137CD8F44F86196F2C42FE45201D9.bin index afcad7c654..8a1b586b16 100644 Binary files a/test_fixtures/masp_proofs/8B29BC2E1A96DF331C7C3A2B227C98D1E5AAAA9988F26B1A47090ACCE693572F.bin and b/test_fixtures/masp_proofs/F534964AD0DF72DF15E18A0FE7629B8C97C137CD8F44F86196F2C42FE45201D9.bin differ diff --git a/test_fixtures/masp_proofs/FBE9AE978398F28195774744E704CB0C9BC7E998E8ADA84DB497859600C70826.bin b/test_fixtures/masp_proofs/FBE9AE978398F28195774744E704CB0C9BC7E998E8ADA84DB497859600C70826.bin new file mode 100644 index 0000000000..fa74b7180b Binary files /dev/null and b/test_fixtures/masp_proofs/FBE9AE978398F28195774744E704CB0C9BC7E998E8ADA84DB497859600C70826.bin differ diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 2c21ea9d97..ca4cfabb1f 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -27,6 +27,7 @@ abciplus = [ ] wasm-runtime = ["namada/wasm-runtime"] +integration = ["namada_apps/integration"] [dependencies] namada = {path = "../shared", features = ["testing"]} @@ -39,6 +40,7 @@ async-trait.workspace = true chrono.workspace = true clap.workspace = true concat-idents.workspace = true +copy_dir = "0.1.3" derivative.workspace = true hyper = {version = "0.14.20", features = ["full"]} lazy_static.workspace = true @@ -66,7 +68,6 @@ data-encoding.workspace = true escargot = {workspace = true} # , features = ["print"]} expectrl.workspace = true eyre.workspace = true -file-serve.workspace = true fs_extra.workspace = true itertools.workspace = true once_cell.workspace = true diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index d85b344f45..538a5d4ae5 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -10,7 +10,7 @@ use expectrl::ControlCode; use namada::eth_bridge::oracle; use namada::eth_bridge::storage::vote_tallies; use namada::ledger::eth_bridge::{ - ContractVersion, Contracts, EthereumBridgeConfig, MinimumConfirmations, + ContractVersion, Contracts, EthereumBridgeParams, MinimumConfirmations, UpgradeableContract, }; use namada::types::address::wnam; @@ -292,7 +292,7 @@ async fn test_bridge_pool_e2e() { let wnam_address = wnam().to_canonical(); let test = setup::network( |mut genesis| { - genesis.ethereum_bridge_params = Some(EthereumBridgeConfig { + genesis.ethereum_bridge_params = Some(EthereumBridgeParams { eth_start_height: Default::default(), min_confirmations: Default::default(), contracts: Contracts { @@ -483,7 +483,7 @@ async fn test_bridge_pool_e2e() { /// other ERC20 transfers. #[tokio::test] async fn test_wnam_transfer() -> Result<()> { - let ethereum_bridge_params = EthereumBridgeConfig { + let ethereum_bridge_params = EthereumBridgeParams { eth_start_height: Default::default(), min_confirmations: MinimumConfirmations::from(unsafe { // SAFETY: The only way the API contract of `NonZeroU64` can @@ -590,7 +590,7 @@ async fn test_wnam_transfer() -> Result<()> { /// storage, if the Ethereum bridge has been bootstrapped for the Namada chain. #[test] fn test_configure_oracle_from_storage() -> Result<()> { - let ethereum_bridge_params = EthereumBridgeConfig { + let ethereum_bridge_params = EthereumBridgeParams { eth_start_height: Default::default(), min_confirmations: MinimumConfirmations::from(unsafe { // SAFETY: The only way the API contract of `NonZeroU64` can diff --git a/tests/src/e2e/eth_bridge_tests/helpers.rs b/tests/src/e2e/eth_bridge_tests/helpers.rs index 04047a1722..9fbc467443 100644 --- a/tests/src/e2e/eth_bridge_tests/helpers.rs +++ b/tests/src/e2e/eth_bridge_tests/helpers.rs @@ -8,7 +8,7 @@ use eyre::{eyre, Context, Result}; use hyper::client::HttpConnector; use hyper::{Body, Client, Method, Request, StatusCode}; use namada::ledger::eth_bridge::{ - wrapped_erc20s, ContractVersion, Contracts, EthereumBridgeConfig, + wrapped_erc20s, ContractVersion, Contracts, EthereumBridgeParams, MinimumConfirmations, UpgradeableContract, }; use namada::types::address::{wnam, Address}; @@ -80,7 +80,7 @@ impl EventsEndpointClient { /// validator that is exposing an endpoint for submission of fake Ethereum /// events. pub fn setup_single_validator_test() -> Result<(Test, NamadaBgCmd)> { - let ethereum_bridge_params = EthereumBridgeConfig { + let ethereum_bridge_params = EthereumBridgeParams { eth_start_height: Default::default(), min_confirmations: MinimumConfirmations::from(unsafe { // SAFETY: The only way the API contract of `NonZeroU64` can diff --git a/tests/src/e2e/helpers.rs b/tests/src/e2e/helpers.rs index 5e4edce6aa..09ec4ab808 100644 --- a/tests/src/e2e/helpers.rs +++ b/tests/src/e2e/helpers.rs @@ -19,7 +19,7 @@ use namada::types::address::Address; use namada::types::key::*; use namada::types::storage::Epoch; use namada::types::token; -use namada_apps::config::genesis::genesis_config; +use namada_apps::config::genesis::chain; use namada_apps::config::utils::convert_tm_addr_to_socket_addr; use namada_apps::config::{Config, TendermintMode}; use namada_core::types::token::NATIVE_MAX_DECIMAL_PLACES; @@ -178,17 +178,19 @@ pub fn get_validator_pk(test: &Test, who: &Who) -> Option { Who::NonValidator => return None, Who::Validator(i) => i, }; - let file = format!("{}.toml", test.net.chain_id.as_str()); - let path = test.test_dir.path().join(file); - let config = genesis_config::open_genesis_config(path).unwrap(); - let pk = config - .validator - .get(&format!("validator-{}", index)) - .unwrap() - .account_public_key - .as_ref() - .unwrap(); - Some(pk.to_public_key().unwrap()) + let path = test.test_dir.path().join("genesis"); + let genesis = chain::Finalized::read_toml_files( + &path.join(test.net.chain_id.as_str()), + ) + .unwrap(); + genesis + .transactions + .validator_account? + .iter() + .find(|val_tx| { + val_tx.tx.alias.to_string() == format!("validator-{}", index) + }) + .map(|val_tx| val_tx.tx.account_key.pk.raw.clone()) } /// Find the address of an account by its alias from the wallet @@ -364,9 +366,6 @@ pub fn generate_bin_command(bin_name: &str, manifest_path: &Path) -> Command { let build_cmd = CargoBuild::new() .package(APPS_PACKAGE) .manifest_path(manifest_path) - // Explicitly disable dev, in case it's enabled when a test is - // invoked - .env("NAMADA_DEV", "false") .bin(bin_name); let build_cmd = if run_debug { diff --git a/tests/src/e2e/ibc_tests.rs b/tests/src/e2e/ibc_tests.rs index 9e8e8d4eba..0b93d0df94 100644 --- a/tests/src/e2e/ibc_tests.rs +++ b/tests/src/e2e/ibc_tests.rs @@ -67,8 +67,9 @@ use namada_apps::client::rpc::{ query_pos_parameters, query_storage_value, query_storage_value_bytes, }; use namada_apps::client::utils::id_from_pk; -use namada_apps::config::ethereum_bridge; -use namada_apps::config::genesis::genesis_config::GenesisConfig; +use namada_apps::config::genesis::{chain, templates}; +use namada_apps::config::utils::set_port; +use namada_apps::config::{ethereum_bridge, TendermintMode}; use namada_apps::facade::tendermint::block::Header as TmHeader; use namada_apps::facade::tendermint::merkle::proof::Proof as TmProof; use namada_apps::facade::tendermint_config::net::Address as TendermintAddress; @@ -82,7 +83,9 @@ use super::setup::set_ethereum_bridge_mode; use crate::e2e::helpers::{ find_address, get_actor_rpc, get_validator_pk, wait_for_wasm_pre_compile, }; -use crate::e2e::setup::{self, sleep, Bin, NamadaCmd, Test, Who}; +use crate::e2e::setup::{ + self, sleep, working_dir, Bin, NamadaCmd, Test, TestDir, Who, +}; use crate::{run, run_as}; #[test] @@ -206,25 +209,92 @@ fn run_ledger_ibc() -> Result<()> { Ok(()) } +/// Set up two Namada chains to talk to each other via IBC. fn setup_two_single_node_nets() -> Result<(Test, Test)> { + const ANOTHER_PROXY_APP: u16 = 27659u16; + const ANOTHER_RPC: u16 = 27660u16; + const ANOTHER_P2P: u16 = 26655u16; // Download the shielded pool parameters before starting node let _ = FsShieldedUtils::new(PathBuf::new()); - // epoch per 100 seconds - let update_genesis = |mut genesis: GenesisConfig| { - genesis.parameters.epochs_per_year = 31536; - genesis.parameters.min_num_of_blocks = 1; - genesis - }; - let update_genesis_b = |mut genesis: GenesisConfig| { - genesis.parameters.epochs_per_year = 31536; - genesis.parameters.min_num_of_blocks = 1; - setup::set_validators(1, genesis, |_| setup::ANOTHER_CHAIN_PORT_OFFSET) + let update_genesis = + |mut genesis: templates::All, base_dir: &_| { + genesis.parameters.parameters.epochs_per_year = 315_360; + setup::set_validators(1, genesis, base_dir, |_| 0) + }; + let test_a = setup::network(update_genesis, None)?; + let test_b = Test { + working_dir: working_dir(), + test_dir: TestDir::new(), + net: test_a.net.clone(), + async_runtime: Default::default(), }; - Ok(( - setup::network(update_genesis, None)?, - setup::network(update_genesis_b, None)?, - )) + for entry in std::fs::read_dir(test_a.test_dir.path()).unwrap() { + let entry = entry.unwrap(); + if entry.path().is_dir() { + copy_dir::copy_dir( + entry.path(), + test_b.test_dir.path().join(entry.file_name()), + ) + .map_err(|e| { + eyre!( + "Failed copying directory from test_a to test_b with {}", + e + ) + })?; + } else { + std::fs::copy( + entry.path(), + test_b.test_dir.path().join(entry.file_name()), + ) + .map_err(|e| { + eyre!("Failed copying file from test_a to test_b with {}", e) + })?; + } + } + let genesis_b_dir = test_b + .test_dir + .path() + .join(namada_apps::client::utils::NET_ACCOUNTS_DIR) + .join("validator-0"); + let mut genesis_b = chain::Finalized::read_toml_files( + &genesis_b_dir.join(test_a.net.chain_id.as_str()), + ) + .map_err(|_| eyre!("Could not read genesis files from test b"))?; + // chain b's validator needs to listen on a different port than chain a's + // validator + let validator_tx = genesis_b + .transactions + .validator_account + .as_mut() + .unwrap() + .iter_mut() + .find(|val| val.tx.alias.to_string() == *"validator-0") + .unwrap(); + let new_port = + validator_tx.tx.net_address.port() + setup::ANOTHER_CHAIN_PORT_OFFSET; + validator_tx.tx.net_address.set_port(new_port); + genesis_b + .write_toml_files(&genesis_b_dir.join(test_a.net.chain_id.as_str())) + .map_err(|_| eyre!("Could not write genesis toml files for test_b"))?; + // modify chain b to use different ports for cometbft + let mut config = namada_apps::config::Config::load( + &genesis_b_dir, + &test_a.net.chain_id, + Some(TendermintMode::Validator), + ); + let proxy_app = &mut config.ledger.cometbft.proxy_app; + set_port(proxy_app, ANOTHER_PROXY_APP); + let rpc_addr = &mut config.ledger.cometbft.rpc.laddr; + set_port(rpc_addr, ANOTHER_RPC); + let p2p_addr = &mut config.ledger.cometbft.p2p.laddr; + set_port(p2p_addr, ANOTHER_P2P); + config + .write(&genesis_b_dir, &test_a.net.chain_id, true) + .map_err(|e| { + eyre!("Unable to modify chain b's config file due to {}", e) + })?; + Ok((test_a, test_b)) } fn create_client(test_a: &Test, test_b: &Test) -> Result<(ClientId, ClientId)> { @@ -1341,7 +1411,7 @@ fn check_balances( ); client.exp_string(&expected)?; // Check the source balance - let expected = ": 900000, owned by albert".to_string(); + let expected = ": 880000, owned by albert".to_string(); client.exp_string(&expected)?; client.assert_success(); @@ -1405,7 +1475,7 @@ fn check_balances_after_back( ); client.exp_string(&expected)?; // Check the source balance - let expected = ": 950000, owned by albert".to_string(); + let expected = ": 930000, owned by albert".to_string(); client.exp_string(&expected)?; client.assert_success(); diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 2ee44c62ae..809feb334d 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -23,15 +23,14 @@ use namada::types::address::Address; use namada::types::storage::Epoch; use namada::types::token; use namada_apps::config::ethereum_bridge; -use namada_apps::config::genesis::genesis_config::{ - GenesisConfig, ParametersConfig, PgfParametersConfig, PosParamsConfig, -}; use namada_apps::config::utils::convert_tm_addr_to_socket_addr; use namada_apps::facade::tendermint_config::net::Address as TendermintAddress; use namada_core::ledger::governance::cli::onchain::{ PgfFunding, PgfFundingTarget, StewardsUpdate, }; +use namada_core::types::token::NATIVE_MAX_DECIMAL_PLACES; use namada_sdk::masp::fs::FsShieldedUtils; +use namada_sdk::wallet::alias::Alias; use namada_test_utils::TestWasms; use namada_vp_prelude::BTreeSet; use serde_json::json; @@ -47,7 +46,10 @@ use crate::e2e::helpers::{ epoch_sleep, find_address, find_bonded_stake, get_actor_rpc, get_epoch, is_debug_mode, parse_reached_epoch, }; -use crate::e2e::setup::{self, default_port_offset, sleep, Bin, Who}; +use crate::e2e::setup::{ + self, allow_duplicate_ips, default_port_offset, set_validators, sleep, Bin, + Who, +}; use crate::{run, run_as}; fn start_namada_ledger_node( @@ -125,10 +127,15 @@ fn run_ledger() -> Result<()> { fn test_node_connectivity_and_consensus() -> Result<()> { // Setup 2 genesis validator nodes let test = setup::network( - |genesis| setup::set_validators(2, genesis, default_port_offset), + |genesis, base_dir| { + setup::set_validators(2, genesis, base_dir, default_port_offset) + }, None, )?; + allow_duplicate_ips(&test, &test.net.chain_id, &Who::Validator(0)); + allow_duplicate_ips(&test, &test.net.chain_id, &Who::Validator(1)); + set_ethereum_bridge_mode( &test, &test.net.chain_id, @@ -210,7 +217,7 @@ fn test_node_connectivity_and_consensus() -> Result<()> { for ledger_rpc in &[validator_0_rpc, validator_1_rpc, non_validator_rpc] { let mut client = run!(test, Bin::Client, query_balance_args(ledger_rpc), Some(40))?; - client.exp_string("nam: 1000010.1")?; + client.exp_string("nam: 980010.1")?; client.assert_success(); } @@ -409,8 +416,7 @@ fn stop_ledger_at_height() -> Result<()> { /// 8. Query the raw bytes of a storage key #[test] fn ledger_txs_and_queries() -> Result<()> { - let test = setup::network(|genesis| genesis, None)?; - + let test = setup::single_node_net()?; set_ethereum_bridge_mode( &test, &test.net.chain_id, @@ -525,7 +531,7 @@ fn ledger_txs_and_queries() -> Result<()> { "init-account", "--public-keys", // Value obtained from `namada::types::key::ed25519::tests::gen_keypair` - "001be519a321e29020fa3cbfbfd01bd5e92db134305609270b71dace25b5a21168", + "pktest1qpqfzxu3gt05jx2mvg82f4anf90psqerkwqhjey4zlqv0qfgwuvkzpklrjh", "--threshold", "1", "--code-path", @@ -594,7 +600,7 @@ fn ledger_txs_and_queries() -> Result<()> { ), // Unspecified token expect all tokens from wallet derived from genesis ( - vec!["balance", "--owner", BERTHA, "--node", &validator_one_rpc], + vec!["balance", "--owner", ALBERT, "--node", &validator_one_rpc], // expect all genesis tokens, sorted by alias vec![ r"apfel: \d+(\.\d+)?", @@ -676,155 +682,6 @@ fn ledger_txs_and_queries() -> Result<()> { Ok(()) } -/// We test shielding, shielded to shielded and unshielding transfers: -/// 1. Run the ledger node -/// 2. Send 20 BTC from Albert to PA(A) -/// 3. Send 7 BTC from SK(A) to PA(B) -/// 4. Assert BTC balance at VK(A) is 13 -/// 5. Send 5 BTC from SK(B) to Bertha -/// 6. Assert BTC balance at VK(B) is 2 -#[test] -fn masp_txs_and_queries() -> Result<()> { - // Download the shielded pool parameters before starting node - let _ = FsShieldedUtils::new(PathBuf::new()); - // Lengthen epoch to ensure that a transaction can be constructed and - // submitted within the same block. Necessary to ensure that conversion is - // not invalidated. - let test = setup::network( - |genesis| { - let parameters = ParametersConfig { - epochs_per_year: epochs_per_year_from_min_duration(3600), - min_num_of_blocks: 1, - ..genesis.parameters - }; - GenesisConfig { - parameters, - ..genesis - } - }, - None, - )?; - set_ethereum_bridge_mode( - &test, - &test.net.chain_id, - &Who::Validator(0), - ethereum_bridge::ledger::Mode::Off, - None, - ); - - // 1. Run the ledger node - let _bg_ledger = - start_namada_ledger_node_wait_wasm(&test, Some(0), Some(40))? - .background(); - - let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); - - let _ep1 = epoch_sleep(&test, &validator_one_rpc, 720)?; - - let txs_args = vec![ - // 2. Send 20 BTC from Albert to PA(A) - ( - vec![ - "transfer", - "--source", - ALBERT, - "--target", - AA_PAYMENT_ADDRESS, - "--token", - BTC, - "--amount", - "20", - "--node", - &validator_one_rpc, - ], - "Transaction is valid", - ), - // 3. Send 7 BTC from SK(A) to PA(B) - ( - vec![ - "transfer", - "--source", - A_SPENDING_KEY, - "--target", - AB_PAYMENT_ADDRESS, - "--token", - BTC, - "--amount", - "7", - "--gas-payer", - CHRISTEL_KEY, - "--node", - &validator_one_rpc, - ], - "Transaction is valid", - ), - // 4. Assert BTC balance at VK(A) is 13 - ( - vec![ - "balance", - "--owner", - AA_VIEWING_KEY, - "--token", - BTC, - "--node", - &validator_one_rpc, - ], - "btc: 13", - ), - // 5. Send 5 BTC from SK(B) to Bertha - ( - vec![ - "transfer", - "--source", - B_SPENDING_KEY, - "--target", - BERTHA, - "--token", - BTC, - "--amount", - "5", - "--gas-payer", - CHRISTEL_KEY, - "--node", - &validator_one_rpc, - ], - "Transaction is valid", - ), - // 6. Assert BTC balance at VK(B) is 2 - ( - vec![ - "balance", - "--owner", - AB_VIEWING_KEY, - "--token", - BTC, - "--node", - &validator_one_rpc, - ], - "btc: 2", - ), - ]; - - for (tx_args, tx_result) in &txs_args { - for &dry_run in &[true, false] { - let tx_args = if dry_run && tx_args[0] == "transfer" { - vec![tx_args.clone(), vec!["--dry-run"]].concat() - } else { - tx_args.clone() - }; - let mut client = run!(test, Bin::Client, tx_args, Some(720))?; - - if *tx_result == "Transaction is valid" && !dry_run { - client.exp_string("Transaction accepted")?; - client.exp_string("Transaction applied")?; - } - client.exp_string(tx_result)?; - } - } - - Ok(()) -} - /// Test the optional disposable keypair for wrapper signing /// /// 1. Test that a tx requesting a disposable signer with a correct unshielding @@ -839,16 +696,11 @@ fn wrapper_disposable_signer() -> Result<()> { // submitted within the same block. Necessary to ensure that conversion is // not invalidated. let test = setup::network( - |genesis| { - let parameters = ParametersConfig { - epochs_per_year: epochs_per_year_from_min_duration(3600), - min_num_of_blocks: 1, - ..genesis.parameters - }; - GenesisConfig { - parameters, - ..genesis - } + |mut genesis, base_dir: &_| { + genesis.parameters.parameters.epochs_per_year = + epochs_per_year_from_min_duration(120); + genesis.parameters.parameters.min_num_of_blocks = 1; + set_validators(1, genesis, base_dir, default_port_offset) }, None, )?; @@ -862,76 +714,69 @@ fn wrapper_disposable_signer() -> Result<()> { let _ep1 = epoch_sleep(&test, &validator_one_rpc, 720)?; - let txs_args = vec![ - ( - vec![ - "transfer", - "--source", - ALBERT, - "--target", - AA_PAYMENT_ADDRESS, - "--token", - NAM, - "--amount", - "50", - "--ledger-address", - &validator_one_rpc, - ], - "Transaction is valid", - ), - ( - vec![ - "transfer", - "--source", - ALBERT, - "--target", - BERTHA, - "--token", - NAM, - "--amount", - "1", - "--gas-spending-key", - A_SPENDING_KEY, - "--disposable-gas-payer", - "--ledger-address", - &validator_one_rpc, - ], - "Transaction is valid", - ), - ( - vec![ - "transfer", - "--source", - ALBERT, - "--target", - BERTHA, - "--token", - NAM, - "--amount", - "1", - "--gas-price", - "90000000", - "--gas-spending-key", - A_SPENDING_KEY, - "--disposable-gas-payer", - "--ledger-address", - &validator_one_rpc, - "--force", - ], - // Not enough funds for fee payment, will use PoW - "Error while processing transaction's fees", - ), + let tx_args = vec![ + "transfer", + "--source", + ALBERT, + "--target", + AA_PAYMENT_ADDRESS, + "--token", + NAM, + "--amount", + "50", + "--ledger-address", + &validator_one_rpc, ]; + let mut client = run!(test, Bin::Client, tx_args, Some(720))?; - for (tx_args, tx_result) in &txs_args { - let mut client = run!(test, Bin::Client, tx_args, Some(720))?; + client.exp_string("Transaction accepted")?; + client.exp_string("Transaction applied")?; + client.exp_string("Transaction is valid")?; - if *tx_result == "Transaction is valid" { - client.exp_string("Transaction accepted")?; - client.exp_string("Transaction applied")?; - } - client.exp_string(tx_result)?; - } + let _ep1 = epoch_sleep(&test, &validator_one_rpc, 720)?; + let tx_args = vec![ + "transfer", + "--source", + ALBERT, + "--target", + BERTHA, + "--token", + NAM, + "--amount", + "1", + "--gas-spending-key", + A_SPENDING_KEY, + "--disposable-gas-payer", + "--ledger-address", + &validator_one_rpc, + ]; + let mut client = run!(test, Bin::Client, tx_args, Some(720))?; + + client.exp_string("Transaction accepted")?; + client.exp_string("Transaction applied")?; + client.exp_string("Transaction is valid")?; + let _ep1 = epoch_sleep(&test, &validator_one_rpc, 720)?; + let tx_args = vec![ + "transfer", + "--source", + ALBERT, + "--target", + BERTHA, + "--token", + NAM, + "--amount", + "1", + "--gas-price", + "90000000", + "--gas-spending-key", + A_SPENDING_KEY, + "--disposable-gas-payer", + "--ledger-address", + &validator_one_rpc, + "--force", + ]; + let mut client = run!(test, Bin::Client, tx_args, Some(720))?; + client.exp_string("Error while processing transaction's fees")?; Ok(()) } @@ -1064,33 +909,31 @@ fn pos_bonds() -> Result<()> { let pipeline_len = 2; let unbonding_len = 4; let test = setup::network( - |genesis| { - let parameters = ParametersConfig { - min_num_of_blocks: 6, - max_expected_time_per_block: 1, - epochs_per_year: 31_536_000, - ..genesis.parameters - }; - let pos_params = PosParamsConfig { - pipeline_len, - unbonding_len, - ..genesis.pos_params - }; - let genesis = GenesisConfig { - parameters, - pos_params, - ..genesis - }; - let mut genesis = - setup::set_validators(2, genesis, default_port_offset); - // Remove stake from the 2nd validator so chain can run with a - // single node - genesis.validator.get_mut("validator-1").unwrap().tokens = None; + |mut genesis, base_dir: &_| { + genesis.parameters.pos_params.pipeline_len = pipeline_len; + genesis.parameters.pos_params.unbonding_len = unbonding_len; + genesis.parameters.parameters.min_num_of_blocks = 6; + genesis.parameters.parameters.max_expected_time_per_block = 1; + genesis.parameters.parameters.epochs_per_year = 31_536_000; + let mut genesis = setup::set_validators( + 2, + genesis, + base_dir, + default_port_offset, + ); + let bonds = genesis.transactions.bond.unwrap(); + genesis.transactions.bond = Some( + bonds + .into_iter() + .filter(|bond| { + (&bond.data.validator).as_ref() != "validator-1" + }) + .collect(), + ); genesis }, None, )?; - set_ethereum_bridge_mode( &test, &test.net.chain_id, @@ -1106,6 +949,27 @@ fn pos_bonds() -> Result<()> { let validator_0_rpc = get_actor_rpc(&test, &Who::Validator(0)); + // put money in the validator account from its balance account so that it + // can self-bond + let tx_args = vec![ + "transfer", + "--source", + "validator-0-balance-key", + "--target", + "validator-0-validator-key", + "--amount", + "100.0", + "--token", + "NAM", + "--node", + &validator_0_rpc, + ]; + let mut client = + run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction applied with result:")?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + // 2. Submit a self-bond for the first genesis validator let tx_args = vec![ "bond", @@ -1114,7 +978,7 @@ fn pos_bonds() -> Result<()> { "--amount", "10000.0", "--signing-keys", - "validator-0-account-key", + "validator-0-validator-key", "--node", &validator_0_rpc, ]; @@ -1172,7 +1036,7 @@ fn pos_bonds() -> Result<()> { "--amount", "5100.0", "--signing-keys", - "validator-0-account-key", + "validator-0-validator-key", "--node", &validator_0_rpc, ]; @@ -1251,7 +1115,7 @@ fn pos_bonds() -> Result<()> { "--validator", "validator-0", "--signing-keys", - "validator-0-account-key", + "validator-0-validator-key", "--node", &validator_0_rpc, ]; @@ -1302,24 +1166,13 @@ fn pos_bonds() -> Result<()> { #[test] fn pos_rewards() -> Result<()> { let test = setup::network( - |genesis| { - let parameters = ParametersConfig { - min_num_of_blocks: 4, - epochs_per_year: 31_536_000, - max_expected_time_per_block: 1, - ..genesis.parameters - }; - let pos_params = PosParamsConfig { - pipeline_len: 2, - unbonding_len: 4, - ..genesis.pos_params - }; - let genesis = GenesisConfig { - parameters, - pos_params, - ..genesis - }; - setup::set_validators(3, genesis, default_port_offset) + |mut genesis, base_dir| { + genesis.parameters.parameters.max_expected_time_per_block = 4; + genesis.parameters.parameters.epochs_per_year = 31_536_000; + genesis.parameters.parameters.max_expected_time_per_block = 1; + genesis.parameters.pos_params.pipeline_len = 2; + genesis.parameters.pos_params.unbonding_len = 4; + setup::set_validators(3, genesis, base_dir, default_port_offset) }, None, )?; @@ -1347,7 +1200,6 @@ fn pos_rewards() -> Result<()> { let validator_zero_rpc = get_actor_rpc(&test, &Who::Validator(0)); let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(1)); - let validator_two_rpc = get_actor_rpc(&test, &Who::Validator(2)); // Submit a delegation from Bertha to validator-0 let tx_args = vec![ @@ -1386,7 +1238,26 @@ fn pos_rewards() -> Result<()> { let _bg_validator_0 = validator_0.background(); let _bg_validator_1 = validator_1.background(); let _bg_validator_2 = validator_2.background(); - + // put money in the validator account from its balance account so that it + // can self-bond + let tx_args = vec![ + "transfer", + "--source", + "validator-1-balance-key", + "--target", + "validator-1-validator-key", + "--amount", + "100.0", + "--token", + "NAM", + "--node", + &validator_one_rpc, + ]; + let mut client = + run_as!(test, Who::Validator(1), Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction applied with result:")?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); // Let validator-1 self-bond let tx_args = vec![ "bond", @@ -1399,7 +1270,7 @@ fn pos_rewards() -> Result<()> { "--gas-token", NAM, "--signing-keys", - "validator-1-account-key", + "validator-1-validator-key", "--ledger-address", &validator_one_rpc, ]; @@ -1408,6 +1279,26 @@ fn pos_rewards() -> Result<()> { client.exp_string("Transaction applied with result:")?; client.exp_string("Transaction is valid.")?; client.assert_success(); + // put money in the validator account from its balance account so that it + // can self-bond + let tx_args = vec![ + "transfer", + "--source", + "validator-2-balance-key", + "--target", + "validator-2-validator-key", + "--amount", + "100.0", + "--token", + "NAM", + "--node", + &validator_one_rpc, + ]; + let mut client = + run_as!(test, Who::Validator(2), Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction applied with result:")?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); // Let validator-2 self-bond let tx_args = vec![ @@ -1421,9 +1312,9 @@ fn pos_rewards() -> Result<()> { "--gas-token", NAM, "--signing-keys", - "validator-2-account-key", + "validator-2-validator-key", "--ledger-address", - &validator_two_rpc, + &validator_zero_rpc, ]; let mut client = run_as!(test, Who::Validator(2), Bin::Client, tx_args, Some(40))?; @@ -1466,23 +1357,13 @@ fn test_bond_queries() -> Result<()> { let pipeline_len = 2; let unbonding_len = 4; let test = setup::network( - |genesis| { - let parameters = ParametersConfig { - min_num_of_blocks: 2, - max_expected_time_per_block: 1, - epochs_per_year: 31_536_000, - ..genesis.parameters - }; - let pos_params = PosParamsConfig { - pipeline_len, - unbonding_len, - ..genesis.pos_params - }; - GenesisConfig { - parameters, - pos_params, - ..genesis - } + |mut genesis, base_dir: &_| { + genesis.parameters.parameters.min_num_of_blocks = 2; + genesis.parameters.parameters.max_expected_time_per_block = 1; + genesis.parameters.parameters.epochs_per_year = 31_536_000; + genesis.parameters.pos_params.pipeline_len = pipeline_len; + genesis.parameters.pos_params.unbonding_len = unbonding_len; + setup::set_validators(1, genesis, base_dir, default_port_offset) }, None, )?; @@ -1495,6 +1376,26 @@ fn test_bond_queries() -> Result<()> { let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); let validator_alias = "validator-0"; + // put money in the validator account from its balance account so that it + // can pay gas fees + let tx_args = vec![ + "transfer", + "--source", + "validator-0-balance-key", + "--target", + "validator-0-validator-key", + "--amount", + "100.0", + "--token", + "NAM", + "--node", + &validator_one_rpc, + ]; + let mut client = + run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + // 2. Submit a delegation to the genesis validator let tx_args = vec![ "bond", @@ -1597,8 +1498,8 @@ fn test_bond_queries() -> Result<()> { let tx_args = vec!["bonds", "--ledger-address", &validator_one_rpc]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; client.exp_string( - "All bonds total active: 200088.000000\r -All bonds total: 200088.000000\r + "All bonds total active: 120188.000000\r +All bonds total: 120188.000000\r All unbonds total active: 412.000000\r All unbonds total: 412.000000\r All unbonds total withdrawable: 412.000000\r", @@ -1620,31 +1521,40 @@ All unbonds total withdrawable: 412.000000\r", #[test] fn pos_init_validator() -> Result<()> { let pipeline_len = 1; - let validator_stake = 200000_u64; + let validator_stake = token::Amount::native_whole(20000_u64); let test = setup::network( - |genesis| { + |mut genesis, base_dir: &_| { + let stake = genesis + .transactions + .bond + .as_ref() + .unwrap() + .iter() + .filter_map(|bond| { + (bond.data.validator.to_string() == *"validator-0").then( + || { + bond.data + .amount + .increase_precision( + NATIVE_MAX_DECIMAL_PLACES.into(), + ) + .unwrap() + .amount + }, + ) + }) + .sum::(); assert_eq!( - genesis.validator.get("validator-0").unwrap().tokens, - Some(validator_stake), + stake, validator_stake, "Assuming this stake, we give the same amount to the new \ validator to have half of voting power", ); - let parameters = ParametersConfig { - min_num_of_blocks: 4, - epochs_per_year: 31_536_000, - max_expected_time_per_block: 1, - ..genesis.parameters - }; - let pos_params = PosParamsConfig { - pipeline_len, - unbonding_len: 2, - ..genesis.pos_params - }; - GenesisConfig { - parameters, - pos_params, - ..genesis - } + genesis.parameters.parameters.min_num_of_blocks = 4; + genesis.parameters.parameters.epochs_per_year = 31_536_000; + genesis.parameters.parameters.max_expected_time_per_block = 1; + genesis.parameters.pos_params.pipeline_len = pipeline_len; + genesis.parameters.pos_params.unbonding_len = 2; + setup::set_validators(1, genesis, base_dir, default_port_offset) }, None, )?; @@ -1727,7 +1637,7 @@ fn pos_init_validator() -> Result<()> { client.assert_success(); // 4. Transfer some NAM to the new validator - let validator_stake_str = &validator_stake.to_string(); + let validator_stake_str = &validator_stake.to_string_native(); let tx_args = vec![ "transfer", "--source", @@ -1814,11 +1724,12 @@ fn pos_init_validator() -> Result<()> { find_bonded_stake(&test, new_validator, &non_validator_rpc)?; assert_eq!( bonded_stake, - token::Amount::native_whole(validator_stake + delegation) + token::Amount::native_whole(delegation) + validator_stake ); Ok(()) } + /// Test that multiple txs submitted in the same block all get the tx result. /// /// In this test we: @@ -1827,7 +1738,9 @@ fn pos_init_validator() -> Result<()> { #[test] fn ledger_many_txs_in_a_block() -> Result<()> { let test = Arc::new(setup::network( - |genesis| genesis, + |genesis, base_dir: &_| { + setup::set_validators(1, genesis, base_dir, |_| 0) + }, // Set 10s consensus timeout to have more time to submit txs Some("10s"), )?); @@ -1911,22 +1824,20 @@ fn ledger_many_txs_in_a_block() -> Result<()> { #[test] fn proposal_submission() -> Result<()> { let test = setup::network( - |genesis| { - let parameters = ParametersConfig { - epochs_per_year: epochs_per_year_from_min_duration(1), - max_proposal_bytes: Default::default(), - min_num_of_blocks: 4, - max_expected_time_per_block: 1, - ..genesis.parameters - }; - - GenesisConfig { - parameters, - ..genesis - } + |mut genesis, base_dir: &_| { + genesis.parameters.gov_params.max_proposal_code_size = 600000; + genesis.parameters.parameters.max_expected_time_per_block = 1; + setup::set_validators(1, genesis, base_dir, |_| 0u16) }, None, )?; + set_ethereum_bridge_mode( + &test, + &test.net.chain_id, + &Who::Validator(0), + ethereum_bridge::ledger::Mode::Off, + None, + ); let namadac_help = vec!["--help"]; @@ -1941,6 +1852,26 @@ fn proposal_submission() -> Result<()> { let validator_0_rpc = get_actor_rpc(&test, &Who::Validator(0)); + // put money in the validator account from its balance account so that it + // can pay gas fees + let tx_args = vec![ + "transfer", + "--source", + "validator-0-balance-key", + "--target", + "validator-0-validator-key", + "--amount", + "100.0", + "--token", + "NAM", + "--node", + &validator_0_rpc, + ]; + let mut client = + run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + // 1.1 Delegate some token let tx_args = vec![ "bond", @@ -1977,6 +1908,7 @@ fn proposal_submission() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; + client.exp_string("Transaction applied")?; client.exp_string("Transaction is valid.")?; client.assert_success(); @@ -2010,7 +1942,7 @@ fn proposal_submission() -> Result<()> { ]; let mut client = run!(test, Bin::Client, query_balance_args, Some(40))?; - client.exp_string("nam: 999500")?; + client.exp_string("nam: 979500")?; client.assert_success(); // 5. Query token balance governance @@ -2077,7 +2009,7 @@ fn proposal_submission() -> Result<()> { ]; let mut client = run!(test, Bin::Client, query_balance_args, Some(40))?; - client.exp_string("nam: 999500")?; + client.exp_string("nam: 979500")?; client.assert_success(); // 9. Send a yay vote from a validator @@ -2163,7 +2095,7 @@ fn proposal_submission() -> Result<()> { let mut client = run!(test, Bin::Client, query_proposal, Some(15))?; client.exp_string("Proposal Id: 0")?; client.exp_string( - "passed with 200900.000000 yay votes and 0.000000 nay votes (0.%)", + "passed with 120900.000000 yay votes and 0.000000 nay votes (0.%)", )?; client.assert_success(); @@ -2185,7 +2117,7 @@ fn proposal_submission() -> Result<()> { ]; let mut client = run!(test, Bin::Client, query_balance_args, Some(30))?; - client.exp_string("nam: 1000000")?; + client.exp_string("nam: 980000")?; client.assert_success(); // 13. Check if governance funds are 0 @@ -2225,24 +2157,16 @@ fn proposal_submission() -> Result<()> { #[test] fn pgf_governance_proposal() -> Result<()> { let test = setup::network( - |genesis| { - let parameters = ParametersConfig { - epochs_per_year: epochs_per_year_from_min_duration(1), - max_proposal_bytes: Default::default(), - min_num_of_blocks: 4, - max_expected_time_per_block: 1, - ..genesis.parameters - }; - let pgf_params = PgfParametersConfig { - stewards: BTreeSet::from_iter(Address::from_str("atest1v4ehgw36xguyzsejx5m5xvehxqmnyvejgdzygv6rgcurgdzxxsunzs3nxuc5vwfkg3pnxdf4u4p9h9")), - ..genesis.pgf_params - }; - - GenesisConfig { - parameters, - pgf_params, - ..genesis - } + |mut genesis, base_dir: &_| { + genesis.parameters.parameters.epochs_per_year = + epochs_per_year_from_min_duration(1); + genesis.parameters.parameters.max_proposal_bytes = + Default::default(); + genesis.parameters.parameters.min_num_of_blocks = 4; + genesis.parameters.parameters.max_expected_time_per_block = 1; + genesis.parameters.pgf_params.stewards = + BTreeSet::from_iter(Alias::from_str("albert")); + setup::set_validators(1, genesis, base_dir, |_| 0) }, None, )?; @@ -2268,6 +2192,26 @@ fn pgf_governance_proposal() -> Result<()> { let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + // put money in the validator account from its balance account so that it + // can pay gas fees + let tx_args = vec![ + "transfer", + "--source", + "validator-0-balance-key", + "--target", + "validator-0-validator-key", + "--amount", + "100.0", + "--token", + "NAM", + "--node", + &validator_one_rpc, + ]; + let mut client = + run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + // Delegate some token let tx_args = vec![ "bond", @@ -2334,7 +2278,7 @@ fn pgf_governance_proposal() -> Result<()> { ]; client = run!(test, Bin::Client, query_balance_args, Some(40))?; - client.exp_string("nam: 999500")?; + client.exp_string("nam: 979500")?; client.assert_success(); // Query token balance governance @@ -2439,7 +2383,7 @@ fn pgf_governance_proposal() -> Result<()> { ]; client = run!(test, Bin::Client, query_balance_args, Some(30))?; - client.exp_string("nam: 1000000")?; + client.exp_string("nam: 980000")?; client.assert_success(); // Check if governance funds are 0 @@ -2543,29 +2487,20 @@ fn pgf_governance_proposal() -> Result<()> { fn proposal_offline() -> Result<()> { let working_dir = setup::working_dir(); let test = setup::network( - |genesis| { - let parameters = ParametersConfig { - epochs_per_year: epochs_per_year_from_min_duration(1), - max_proposal_bytes: Default::default(), - min_num_of_blocks: 4, - max_expected_time_per_block: 1, - vp_whitelist: Some(get_all_wasms_hashes( - &working_dir, - Some("vp_"), - )), - // Enable tx whitelist to test the execution of a - // non-whitelisted tx by governance - tx_whitelist: Some(get_all_wasms_hashes( - &working_dir, - Some("tx_"), - )), - ..genesis.parameters - }; - - GenesisConfig { - parameters, - ..genesis - } + |mut genesis, base_dir: &_| { + genesis.parameters.parameters.epochs_per_year = + epochs_per_year_from_min_duration(1); + genesis.parameters.parameters.max_proposal_bytes = + Default::default(); + genesis.parameters.parameters.min_num_of_blocks = 4; + genesis.parameters.parameters.max_expected_time_per_block = 1; + genesis.parameters.parameters.vp_whitelist = + Some(get_all_wasms_hashes(&working_dir, Some("vp_"))); + // Enable tx whitelist to test the execution of a + // non-whitelisted tx by governance + genesis.parameters.parameters.tx_whitelist = + Some(get_all_wasms_hashes(&working_dir, Some("tx_"))); + setup::set_validators(1, genesis, base_dir, |_| 0) }, None, )?; @@ -2695,7 +2630,7 @@ fn proposal_offline() -> Result<()> { let mut client = run!(test, Bin::Client, tally_offline, Some(15))?; client.exp_string("Parsed 1 votes")?; - client.exp_string("rejected with 900.000000 yay votes")?; + client.exp_string("rejected with 20900.000000 yay votes")?; client.assert_success(); Ok(()) @@ -2715,407 +2650,6 @@ fn generate_proposal_json_file( serde_json::to_writer(intent_writer, proposal_content).unwrap(); } -/// In this test we: -/// 1. Setup 2 genesis validators -/// 2. Initialize a new network with the 2 validators -/// 3. Setup and start the 2 genesis validator nodes and a non-validator node -/// 4. Submit a valid token transfer tx from one validator to the other -/// 5. Check that all the nodes processed the tx with the same result -#[test] -fn test_genesis_validators() -> Result<()> { - use std::collections::HashMap; - use std::net::SocketAddr; - use std::str::FromStr; - - use namada::types::chain::ChainId; - use namada_apps::config::genesis::genesis_config::{ - self, ValidatorPreGenesisConfig, - }; - use namada_apps::config::Config; - - // This test is not using the `setup::network`, because we're setting up - // custom genesis validators - setup::INIT.call_once(|| { - if let Err(err) = color_eyre::install() { - eprintln!("Failed setting up colorful error reports {}", err); - } - }); - - let working_dir = setup::working_dir(); - let test_dir = setup::TestDir::new(); - let checksums_path = working_dir - .join("wasm/checksums.json") - .to_string_lossy() - .into_owned(); - - // Same as in `genesis/e2e-tests-single-node.toml` for `validator-0` - let net_address_0 = SocketAddr::from_str("127.0.0.1:27656").unwrap(); - let net_address_port_0 = net_address_0.port(); - // Find the first port (ledger P2P) that should be used for a validator at - // the given index - let get_first_port = |ix: u8| net_address_port_0 + 6 * (ix as u16 + 1); - - // 1. Setup 2 genesis validators, one with ed25519 keys (0) and one with - // secp256k1 keys (1) - let validator_0_alias = "validator-0"; - let validator_1_alias = "validator-1"; - - let mut init_genesis_validator_0 = setup::run_cmd( - Bin::Client, - [ - "utils", - "init-genesis-validator", - "--unsafe-dont-encrypt", - "--alias", - validator_0_alias, - "--scheme", - "ed25519", - "--commission-rate", - "0.05", - "--max-commission-rate-change", - "0.01", - "--net-address", - &format!("127.0.0.1:{}", get_first_port(0)), - ], - Some(5), - &working_dir, - &test_dir, - format!("{}:{}", std::file!(), std::line!()), - )?; - init_genesis_validator_0.assert_success(); - let validator_0_pre_genesis_dir = - namada_apps::client::utils::validator_pre_genesis_dir( - test_dir.path(), - validator_0_alias, - ); - let config = std::fs::read_to_string( - namada_apps::client::utils::validator_pre_genesis_file( - &validator_0_pre_genesis_dir, - ), - ) - .unwrap(); - let mut validator_0_config: ValidatorPreGenesisConfig = - toml::from_str(&config).unwrap(); - let validator_0_config = validator_0_config - .validator - .remove(validator_0_alias) - .unwrap(); - - let mut init_genesis_validator_1 = setup::run_cmd( - Bin::Client, - [ - "utils", - "init-genesis-validator", - "--unsafe-dont-encrypt", - "--alias", - validator_1_alias, - "--scheme", - "secp256k1", - "--commission-rate", - "0.05", - "--max-commission-rate-change", - "0.01", - "--net-address", - &format!("127.0.0.1:{}", get_first_port(1)), - ], - Some(5), - &working_dir, - &test_dir, - format!("{}:{}", std::file!(), std::line!()), - )?; - init_genesis_validator_1.assert_success(); - let validator_1_pre_genesis_dir = - namada_apps::client::utils::validator_pre_genesis_dir( - test_dir.path(), - validator_1_alias, - ); - let config = std::fs::read_to_string( - namada_apps::client::utils::validator_pre_genesis_file( - &validator_1_pre_genesis_dir, - ), - ) - .unwrap(); - let mut validator_1_config: ValidatorPreGenesisConfig = - toml::from_str(&config).unwrap(); - let validator_1_config = validator_1_config - .validator - .remove(validator_1_alias) - .unwrap(); - - // 2. Initialize a new network with the 2 validators - let mut genesis = genesis_config::open_genesis_config( - working_dir.join(setup::SINGLE_NODE_NET_GENESIS), - )?; - let update_validator_config = - |ix: u8, mut config: genesis_config::ValidatorConfig| { - // Setup tokens balances and validity predicates - config.tokens = Some(200000); - config.non_staked_balance = Some(1000000000000); - config.validator_vp = Some("vp_user".into()); - // Setup the validator ports same as what - // `setup::set_validators` would do - let mut net_address = net_address_0; - // 6 ports for each validator - let first_port = get_first_port(ix); - net_address.set_port(first_port); - config.net_address = Some(net_address.to_string()); - config - }; - genesis.validator = HashMap::from_iter([ - ( - validator_0_alias.to_owned(), - update_validator_config(0, validator_0_config), - ), - ( - validator_1_alias.to_owned(), - update_validator_config(1, validator_1_config), - ), - ]); - let genesis_file = test_dir.path().join("e2e-test-genesis-src.toml"); - genesis_config::write_genesis_config(&genesis, &genesis_file); - let genesis_path = genesis_file.to_string_lossy(); - - let archive_dir = test_dir.path().to_string_lossy().to_string(); - let args = vec![ - "utils", - "init-network", - "--unsafe-dont-encrypt", - "--genesis-path", - &genesis_path, - "--chain-prefix", - "e2e-test", - "--localhost", - "--allow-duplicate-ip", - "--wasm-checksums-path", - &checksums_path, - "--archive-dir", - &archive_dir, - ]; - let mut init_network = setup::run_cmd( - Bin::Client, - args, - Some(5), - &working_dir, - &test_dir, - format!("{}:{}", std::file!(), std::line!()), - )?; - - // Get the generated chain_id` from result of the last command - let (unread, matched) = - init_network.exp_regex(r"Derived chain ID: .*\n")?; - let chain_id_raw = - matched.trim().split_once("Derived chain ID: ").unwrap().1; - let chain_id = ChainId::from_str(chain_id_raw.trim())?; - println!("'init-network' output: {}", unread); - let net = setup::Network { - chain_id: chain_id.clone(), - }; - let test = setup::Test { - working_dir: working_dir.clone(), - test_dir, - net, - genesis, - async_runtime: Default::default(), - }; - - // Host the network archive to make it available for `join-network` commands - let network_archive_server = file_serve::Server::new(&working_dir); - let network_archive_addr = network_archive_server.addr().to_owned(); - std::thread::spawn(move || { - network_archive_server.serve().unwrap(); - }); - - // 3. Setup and start the 2 genesis validator nodes and a non-validator node - - // Clean-up the chain dir from the existing validator dir that were created - // by `init-network`, because we want to set them up with `join-network` - // instead - let validator_0_base_dir = test.get_base_dir(&Who::Validator(0)); - let validator_1_base_dir = test.get_base_dir(&Who::Validator(1)); - std::fs::remove_dir_all(&validator_0_base_dir).unwrap(); - std::fs::remove_dir_all(&validator_1_base_dir).unwrap(); - - std::env::set_var( - namada_apps::client::utils::ENV_VAR_NETWORK_CONFIGS_SERVER, - format!("http://{network_archive_addr}/{}", archive_dir), - ); - let pre_genesis_path = validator_0_pre_genesis_dir.to_string_lossy(); - let mut join_network_val_0 = run_as!( - test, - Who::Validator(0), - Bin::Client, - [ - "utils", - "join-network", - "--chain-id", - chain_id.as_str(), - "--pre-genesis-path", - pre_genesis_path.as_ref(), - "--dont-prefetch-wasm", - ], - Some(5) - )?; - join_network_val_0.exp_string("Successfully configured for chain")?; - - let pre_genesis_path = validator_1_pre_genesis_dir.to_string_lossy(); - let mut join_network_val_1 = run_as!( - test, - Who::Validator(1), - Bin::Client, - [ - "utils", - "join-network", - "--chain-id", - chain_id.as_str(), - "--pre-genesis-path", - pre_genesis_path.as_ref(), - "--dont-prefetch-wasm", - ], - Some(5) - )?; - join_network_val_1.exp_string("Successfully configured for chain")?; - - // We have to update the ports in the configs again, because the ones from - // `join-network` use the defaults - // - // TODO: use `update_actor_config` from `setup`, instead - let update_config = |ix: u8, mut config: Config| { - let first_port = net_address_port_0 + 6 * (ix as u16 + 1); - let p2p_addr = - convert_tm_addr_to_socket_addr(&config.ledger.cometbft.p2p.laddr) - .ip() - .to_string(); - - config.ledger.cometbft.p2p.laddr = TendermintAddress::from_str( - &format!("{}:{}", p2p_addr, first_port), - ) - .unwrap(); - let rpc_addr = - convert_tm_addr_to_socket_addr(&config.ledger.cometbft.rpc.laddr) - .ip() - .to_string(); - config.ledger.cometbft.rpc.laddr = TendermintAddress::from_str( - &format!("{}:{}", rpc_addr, first_port + 1), - ) - .unwrap(); - let proxy_app_addr = - convert_tm_addr_to_socket_addr(&config.ledger.cometbft.proxy_app) - .ip() - .to_string(); - config.ledger.cometbft.proxy_app = TendermintAddress::from_str( - &format!("{}:{}", proxy_app_addr, first_port + 2), - ) - .unwrap(); - config - }; - - let validator_0_config = update_config( - 0, - Config::load(&validator_0_base_dir, &test.net.chain_id, None), - ); - validator_0_config - .write(&validator_0_base_dir, &chain_id, true) - .unwrap(); - - let validator_1_config = update_config( - 1, - Config::load(&validator_1_base_dir, &test.net.chain_id, None), - ); - validator_1_config - .write(&validator_1_base_dir, &chain_id, true) - .unwrap(); - - // Copy WASMs to each node's chain dir - let chain_dir = test.test_dir.path().join(chain_id.as_str()); - setup::copy_wasm_to_chain_dir( - &working_dir, - &chain_dir, - &chain_id, - test.genesis.validator.keys(), - ); - - let mut validator_0 = - start_namada_ledger_node_wait_wasm(&test, Some(0), Some(40))?; - let mut validator_1 = - start_namada_ledger_node_wait_wasm(&test, Some(1), Some(40))?; - let mut non_validator = - start_namada_ledger_node_wait_wasm(&test, None, Some(40))?; - - // Wait for a first block - validator_0.exp_string("Committed block hash")?; - validator_1.exp_string("Committed block hash")?; - non_validator.exp_string("Committed block hash")?; - - let bg_validator_0 = validator_0.background(); - let bg_validator_1 = validator_1.background(); - let _bg_non_validator = non_validator.background(); - - // 4. Submit a valid token transfer tx - let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); - let tx_args = [ - "transfer", - "--source", - validator_0_alias, - "--target", - validator_1_alias, - "--token", - NAM, - "--amount", - "10.1", - "--node", - &validator_one_rpc, - ]; - let mut client = - run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction applied with result:")?; - client.exp_string("Transaction is valid.")?; - client.assert_success(); - - // 3. Check that all the nodes processed the tx with the same result - let mut validator_0 = bg_validator_0.foreground(); - let mut validator_1 = bg_validator_1.foreground(); - - let expected_result = "successful inner txs: 1"; - // We cannot check this on non-validator node as it might sync without - // applying the tx itself, but its state should be the same, checked below. - validator_0.exp_string(expected_result)?; - validator_1.exp_string(expected_result)?; - let _bg_validator_0 = validator_0.background(); - let _bg_validator_1 = validator_1.background(); - - let validator_0_rpc = get_actor_rpc(&test, &Who::Validator(0)); - let validator_1_rpc = get_actor_rpc(&test, &Who::Validator(1)); - let non_validator_rpc = get_actor_rpc(&test, &Who::NonValidator); - - // Find the block height on the validator - let after_tx_height = get_height(&test, &validator_0_rpc)?; - - // Wait for the second validator and non-validator to be synced to at least - // the same height - wait_for_block_height(&test, &validator_1_rpc, after_tx_height, 10)?; - wait_for_block_height(&test, &non_validator_rpc, after_tx_height, 10)?; - - let query_balance_args = |ledger_rpc| { - vec![ - "balance", - "--owner", - validator_1_alias, - "--token", - NAM, - "--node", - ledger_rpc, - ] - }; - for ledger_rpc in &[validator_0_rpc, validator_1_rpc, non_validator_rpc] { - let mut client = - run!(test, Bin::Client, query_balance_args(ledger_rpc), Some(40))?; - client.exp_string(r"nam: 1000000000010.1")?; - client.assert_success(); - } - - Ok(()) -} - /// In this test we intentionally make a validator node double sign blocks /// to test that slashing evidence is received and processed by the ledger /// correctly: @@ -3141,21 +2675,22 @@ fn double_signing_gets_slashed() -> Result<()> { // Setup 2 genesis validator nodes let test = setup::network( - |genesis| { + |mut genesis, base_dir| { (pipeline_len, unbonding_len, cubic_offset) = ( - genesis.pos_params.pipeline_len, - genesis.pos_params.unbonding_len, - genesis.pos_params.cubic_slashing_window_length, + genesis.parameters.pos_params.pipeline_len, + genesis.parameters.pos_params.unbonding_len, + genesis.parameters.pos_params.cubic_slashing_window_length, ); - let mut genesis = - setup::set_validators(4, genesis, default_port_offset); // Make faster epochs to be more likely to discover boundary issues - genesis.parameters.min_num_of_blocks = 2; - genesis + genesis.parameters.parameters.min_num_of_blocks = 2; + setup::set_validators(4, genesis, base_dir, default_port_offset) }, None, )?; + allow_duplicate_ips(&test, &test.net.chain_id, &Who::Validator(0)); + allow_duplicate_ips(&test, &test.net.chain_id, &Who::Validator(1)); + set_ethereum_bridge_mode( &test, &test.net.chain_id, @@ -3192,6 +2727,28 @@ fn double_signing_gets_slashed() -> Result<()> { validator_3.exp_string("This node is a validator")?; let _bg_validator_3 = validator_3.background(); + let validator_zero_rpc = get_actor_rpc(&test, &Who::Validator(0)); + // put money in the validator account from its balance account so that it + // can pay gas fees + let tx_args = vec![ + "transfer", + "--source", + "validator-0-balance-key", + "--target", + "validator-0-validator-key", + "--amount", + "100.0", + "--token", + "NAM", + "--node", + &validator_zero_rpc, + ]; + let mut client = + run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction is valid.")?; + + client.assert_success(); + // 2. Copy the first genesis validator base-dir let validator_0_base_dir = test.get_base_dir(&Who::Validator(0)); let validator_0_base_dir_copy = test @@ -3437,7 +2994,7 @@ fn double_signing_gets_slashed() -> Result<()> { /// 2d. Submit same tx again, this time the client shouldn't reveal again. #[test] fn implicit_account_reveal_pk() -> Result<()> { - let test = setup::network(|genesis| genesis, None)?; + let test = setup::single_node_net()?; // 1. Run the ledger node let _bg_ledger = @@ -3568,16 +3125,11 @@ fn implicit_account_reveal_pk() -> Result<()> { fn test_epoch_sleep() -> Result<()> { // Use slightly longer epochs to give us time to sleep let test = setup::network( - |genesis| { - let parameters = ParametersConfig { - epochs_per_year: epochs_per_year_from_min_duration(30), - min_num_of_blocks: 1, - ..genesis.parameters - }; - GenesisConfig { - parameters, - ..genesis - } + |mut genesis, base_dir: &_| { + genesis.parameters.parameters.epochs_per_year = + epochs_per_year_from_min_duration(30); + genesis.parameters.parameters.min_num_of_blocks = 1; + setup::set_validators(1, genesis, base_dir, |_| 0) }, None, )?; diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index 9a58230072..5e6fb9787f 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use std::ffi::OsStr; use std::fmt::Display; -use std::fs::{File, OpenOptions}; +use std::fs::{create_dir_all, File, OpenOptions}; use std::path::{Path, PathBuf}; use std::process::Command; use std::str::FromStr; @@ -16,13 +16,22 @@ use expectrl::process::unix::{PtyStream, UnixProcess}; use expectrl::session::Session; use expectrl::stream::log::LogStream; use expectrl::{ControlCode, Eof, WaitStatus}; -use eyre::{eyre, Context}; +use eyre::eyre; use itertools::{Either, Itertools}; use namada::types::chain::ChainId; -use namada_apps::client::utils; -use namada_apps::config::genesis::genesis_config::{self, GenesisConfig}; -use namada_apps::config::{ethereum_bridge, Config}; +use namada_apps::client::utils::{ + self, validator_pre_genesis_dir, validator_pre_genesis_txs_file, +}; +use namada_apps::config::genesis::toml_utils::read_toml; +use namada_apps::config::genesis::{chain, templates}; +use namada_apps::config::{ethereum_bridge, genesis, Config}; use namada_apps::{config, wallet}; +use namada_core::types::key::{RefTo, SchemeType}; +use namada_core::types::string_encoding::StringEncoded; +use namada_core::types::token::NATIVE_MAX_DECIMAL_PLACES; +use namada_sdk::wallet::alias::Alias; +use namada_tx_prelude::token; +use namada_vp_prelude::HashSet; use once_cell::sync::Lazy; use rand::Rng; use serde_json; @@ -54,9 +63,10 @@ pub const ENV_VAR_USE_PREBUILT_BINARIES: &str = /// This file must contain a single validator with alias "validator-0". /// To add more validators, use the [`set_validators`] function in the call to /// setup the [`network`]. -pub const SINGLE_NODE_NET_GENESIS: &str = "genesis/e2e-tests-single-node.toml"; +#[allow(dead_code)] +pub const SINGLE_NODE_NET_GENESIS: &str = "genesis/localnet"; /// An E2E test network. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Network { pub chain_id: ChainId, } @@ -88,6 +98,13 @@ pub fn update_actor_config( .unwrap(); } +/// Configure validator p2p settings to allow duplicat ips +pub fn allow_duplicate_ips(test: &Test, chain_id: &ChainId, who: &Who) { + update_actor_config(test, chain_id, who, |config| { + config.ledger.cometbft.p2p.allow_duplicate_ip = true; + }); +} + /// Configures the Ethereum bridge mode of `who`. This should be done before /// `who` starts running. pub fn set_ethereum_bridge_mode( @@ -112,40 +129,145 @@ pub fn set_ethereum_bridge_mode( /// INVARIANT: Do not call this function more than once on the same config. pub fn set_validators( num: u8, - mut genesis: GenesisConfig, + mut genesis: templates::All, + base_dir: &Path, port_offset: F, -) -> GenesisConfig +) -> templates::All where F: Fn(u8) -> u16, { - let validator_0 = genesis.validator.get_mut("validator-0").unwrap(); - // Clone the first validator before modifying it - let other_validators = validator_0.clone(); - let validator_0_target = validator_0.net_address.clone().unwrap(); - let split: Vec<&str> = validator_0_target.split(':').collect(); - let (net_target_0, net_address_port_0) = - (split[0], split[1].parse::().unwrap()); - for ix in 0..num { - let mut validator = other_validators.clone(); - let mut net_target = net_target_0.to_string(); - // 6 ports for each validator - let first_port = net_address_port_0 + port_offset(ix); - net_target = format!("{}:{}", net_target, first_port); - validator.net_address = Some(net_target.to_string()); - let name = format!("validator-{}", ix); - genesis.validator.insert(name, validator); + // for each validator: + // - generate a balance key + // - assign balance to the key + // - invoke `init-genesis-validator` signed by balance key to generate + // validator pre-genesis wallet signed genesis txs + // - add txs to genesis templates + let wallet_path = base_dir.join("pre-genesis"); + for val in 0..num { + // generate a balance key + let mut wallet = wallet::load(&wallet_path) + .expect("Could not locate pre-genesis wallet used for e2e tests."); + let alias = format!("validator-{}-balance-key", val); + let (alias, sk, _mnemonic) = wallet + .gen_key(SchemeType::Ed25519, Some(alias), true, None, None, None) + .unwrap_or_else(|_| { + panic!("Could not generate new key for validator-{}", val) + }); + wallet::save(&wallet).unwrap(); + // assign balance to the key + genesis + .balances + .token + .get_mut(&Alias::from_str("nam").expect("Infallible")) + .expect("NAM balances should exist in pre-genesis wallet already") + .0 + .insert( + StringEncoded::new(sk.ref_to()), + token::DenominatedAmount { + amount: token::Amount::from_uint( + 3000000, + NATIVE_MAX_DECIMAL_PLACES, + ) + .unwrap(), + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }, + ); + // invoke `init-genesis-validator` signed by balance key to generate + // validator pre-genesis wallet signed genesis txs + let validator_alias = format!("validator-{}", val); + let net_addr = format!("127.0.0.1:{}", 27656 + port_offset(val)); + let args = vec![ + "utils", + "init-genesis-validator", + "--source", + &alias, + "--alias", + &validator_alias, + "--net-address", + &net_addr, + "--commission-rate", + "0.05", + "--max-commission-rate-change", + "0.01", + "--transfer-from-source-amount", + "2000000", + "--self-bond-amount", + "100000", + "--unsafe-dont-encrypt", + ]; + let validator_alias = format!("validator-{}", val); + // initialize the validator + let mut init_genesis_validator = run_cmd( + Bin::Client, + args, + Some(5), + &working_dir(), + base_dir, + format!("{}:{}", std::file!(), std::line!()), + ) + .unwrap(); + init_genesis_validator.assert_success(); + // add generated txs to genesis + let pre_genesis_path = + validator_pre_genesis_dir(base_dir, &validator_alias); + let pre_genesis_tx_path = + validator_pre_genesis_txs_file(&pre_genesis_path); + let pre_genesis_txs = + read_toml(&pre_genesis_tx_path, "transactions.toml").unwrap(); + genesis.transactions.merge(pre_genesis_txs); + // move validators generated files to their own base dir + let validator_base_dir = base_dir + .join(utils::NET_ACCOUNTS_DIR) + .join(&validator_alias); + let src_path = validator_pre_genesis_dir(base_dir, &validator_alias); + let dest_path = + validator_pre_genesis_dir(&validator_base_dir, &validator_alias); + println!( + "{} for {validator_alias} from {} to {}.", + "Copying pre-genesis validator-wallet".yellow(), + src_path.to_string_lossy(), + dest_path.to_string_lossy(), + ); + fs::create_dir_all(&dest_path).unwrap(); + fs::rename(src_path, dest_path).unwrap(); } genesis } +/// Remove self-bonds from default templates. They will be +/// regenerated later. +fn remove_self_bonds(genesis: &mut templates::All) { + let bonds = genesis.transactions.bond.take().unwrap(); + genesis.transactions.bond = Some( + bonds + .into_iter() + .filter(|bond| { + if let genesis::transactions::AliasOrPk::Alias(alias) = + &bond.data.source + { + *alias != bond.data.validator + } else { + true + } + }) + .collect(), + ); +} + /// Setup a network with a single genesis validator node. pub fn single_node_net() -> Result { - network(|genesis| genesis, None) + network( + |genesis, base_dir: &_| set_validators(1, genesis, base_dir, |_| 0u16), + None, + ) } /// Setup a configurable network. pub fn network( - mut update_genesis: impl FnMut(GenesisConfig) -> GenesisConfig, + mut update_genesis: impl FnMut( + templates::All, + &Path, + ) -> templates::All, consensus_timeout_commit: Option<&'static str>, ) -> Result { INIT.call_once(|| { @@ -156,41 +278,86 @@ pub fn network( let working_dir = working_dir(); let test_dir = TestDir::new(); - // Open the source genesis file - let mut genesis = genesis_config::open_genesis_config( - working_dir.join(SINGLE_NODE_NET_GENESIS), - )?; - - genesis.parameters.vp_whitelist = + // Open the source genesis file templates + let templates_dir = working_dir.join("genesis").join("localnet"); + println!( + "{} {}.", + "Loading genesis templates from".yellow(), + templates_dir.to_string_lossy() + ); + let mut templates = + genesis::templates::All::read_toml_files(&templates_dir) + .unwrap_or_else(|_| { + panic!( + "Missing genesis templates files at {}", + templates_dir.to_string_lossy() + ) + }); + // clear existing validator txs from genesis + templates.transactions.validator_account = None; + // remove self-bonds from genesis + remove_self_bonds(&mut templates); + + // Update the templates as needed + templates.parameters.parameters.vp_whitelist = Some(get_all_wasms_hashes(&working_dir, Some("vp_"))); - genesis.parameters.tx_whitelist = + templates.parameters.parameters.tx_whitelist = Some(get_all_wasms_hashes(&working_dir, Some("tx_"))); + // Copy the main wallet from templates dir into the base dir. + { + let base_dir = test_dir.path(); + let src_path = + wallet::wallet_file(&templates_dir.join("src").join("pre-genesis")); + let dest_dir = base_dir.join("pre-genesis"); + let dest_path = wallet::wallet_file(&dest_dir); + println!( + "{} from {} to {}.", + "Copying main pre-genesis wallet into a default non-validator \ + base dir" + .yellow(), + src_path.to_string_lossy(), + dest_path.to_string_lossy(), + ); + fs::create_dir_all(&dest_dir)?; + fs::copy(&src_path, &dest_path)?; + } // Run the provided function on it - let genesis = update_genesis(genesis); + let templates = update_genesis(templates, test_dir.path()); + + // Write the updated genesis templates to the test dir + let updated_templates_dir = test_dir.path().join("templates"); + create_dir_all(&updated_templates_dir)?; + println!( + "{} {}.", + "Writing updated genesis templates to".yellow(), + updated_templates_dir.to_string_lossy() + ); + templates.write_toml_files(&updated_templates_dir)?; - // Run `init-network` to generate the finalized genesis config, keys and - // addresses and update WASM checksums - let genesis_file = test_dir.path().join("e2e-test-genesis-src.toml"); - genesis_config::write_genesis_config(&genesis, &genesis_file); - let genesis_path = genesis_file.to_string_lossy(); + // Run `init-network` on the updated templates to generate the finalized + // genesis config and addresses and update WASM checksums + let templates_path = updated_templates_dir.to_string_lossy().into_owned(); + println!("{}", "Finalizing network from genesis templates.".yellow()); let checksums_path = working_dir .join("wasm/checksums.json") .to_string_lossy() .into_owned(); + let genesis_dir = test_dir.path().join("genesis"); + let archive_dir = genesis_dir.to_string_lossy().to_string(); let mut args = vec![ "utils", "init-network", - "--unsafe-dont-encrypt", - "--genesis-path", - &genesis_path, + "--templates-path", + &templates_path, "--chain-prefix", "e2e-test", - "--localhost", - "--dont-archive", - "--allow-duplicate-ip", "--wasm-checksums-path", &checksums_path, + "--genesis-time", + "2023-08-30T00:00:00Z", + "--archive-dir", + &archive_dir, ]; if let Some(consensus_timeout_commit) = consensus_timeout_commit { args.push("--consensus-timeout-commit"); @@ -201,7 +368,7 @@ pub fn network( args, Some(5), &working_dir, - &test_dir, + &genesis_dir, format!("{}:{}", std::file!(), std::line!()), )?; @@ -211,38 +378,116 @@ pub fn network( let chain_id_raw = matched.trim().split_once("Derived chain ID: ").unwrap().1; let chain_id = ChainId::from_str(chain_id_raw.trim())?; - println!("'init-network' output: {}", unread); + println!("'init-network' unread output: {}", unread); let net = Network { chain_id }; + init_network.assert_success(); - // release lock on wallet by dropping the - // child process drop(init_network); - // Move the "others" accounts wallet in the main base dir, so that we can - // use them with `Who::NonValidator` - let chain_dir = test_dir.path().join(net.chain_id.as_str()); - std::fs::rename( - wallet::wallet_file( - chain_dir - .join(utils::NET_ACCOUNTS_DIR) - .join(utils::NET_OTHER_ACCOUNTS_DIR), - ), - wallet::wallet_file(chain_dir.clone()), + // Set the network archive dir to make it available for `join-network` + // commands + std::env::set_var( + namada_apps::client::utils::ENV_VAR_NETWORK_CONFIGS_DIR, + archive_dir, + ); + + let genesis_new = chain::Finalized::read_toml_files( + &genesis_dir.join(net.chain_id.as_str()), ) .unwrap(); + let validator_aliases = genesis_new + .transactions + .validator_account + .as_ref() + .map(|txs| { + txs.iter().fold(HashSet::new(), |mut acc, finalized| { + acc.insert(finalized.tx.alias.to_string()); + acc + }) + }) + .unwrap_or_default(); - copy_wasm_to_chain_dir( - &working_dir, - &chain_dir, - &net.chain_id, - genesis.validator.keys(), - ); + // Setup a dir for every validator and non-validator using their + // pre-genesis wallets + for alias in &validator_aliases { + let validator_base_dir = + test_dir.path().join(utils::NET_ACCOUNTS_DIR).join(alias); + + // Copy the main wallet from templates dir into validator's base dir. + { + let dest_dir = validator_base_dir.join("pre-genesis"); + let dest_path = wallet::wallet_file(&dest_dir); + let base_dir = test_dir.path(); + let src_dir = base_dir.join("pre-genesis"); + let src_path = wallet::wallet_file(&src_dir); + println!( + "{} for {alias} from {} to {}.", + "Copying main pre-genesis wallet".yellow(), + src_path.to_string_lossy(), + dest_path.to_string_lossy(), + ); + fs::create_dir_all(&dest_dir)?; + fs::copy(&src_path, &dest_path)?; + } + println!("{} {}.", "Joining network with ".yellow(), alias); + let validator_base_dir = + test_dir.path().join(utils::NET_ACCOUNTS_DIR).join(alias); + let mut join_network = run_cmd( + Bin::Client, + [ + "utils", + "join-network", + "--chain-id", + net.chain_id.as_str(), + "--genesis-validator", + alias, + "--dont-prefetch-wasm", + ], + Some(5), + &working_dir, + &validator_base_dir, + format!("{}:{}", std::file!(), std::line!()), + )?; + join_network.exp_string("Successfully configured for chain")?; + join_network.assert_success(); + copy_wasm_to_chain_dir( + &working_dir, + &validator_base_dir, + &net.chain_id, + ); + } + + // Setup a dir for a non-validator using the pre-genesis wallet + { + let base_dir = test_dir.path(); + println!( + "{}.", + "Joining network with a default non-validator node".yellow() + ); + let mut join_network = run_cmd( + Bin::Client, + [ + "utils", + "join-network", + "--chain-id", + net.chain_id.as_str(), + "--dont-prefetch-wasm", + ], + Some(5), + &working_dir, + base_dir, + format!("{}:{}", std::file!(), std::line!()), + )?; + join_network.exp_string("Successfully configured for chain")?; + join_network.assert_success(); + } + + copy_wasm_to_chain_dir(&working_dir, test_dir.path(), &net.chain_id); Ok(Test { working_dir, test_dir, net, - genesis, async_runtime: Default::default(), }) } @@ -265,7 +510,6 @@ pub struct Test { /// Namada cmds pub test_dir: TestDir, pub net: Network, - pub genesis: GenesisConfig, pub async_runtime: LazyAsyncRuntime, } @@ -455,10 +699,8 @@ impl Test { Who::Validator(index) => self .test_dir .path() - .join(self.net.chain_id.as_str()) .join(utils::NET_ACCOUNTS_DIR) - .join(format!("validator-{}", index)) - .join(config::DEFAULT_BASE_DIR), + .join(format!("validator-{}", index)), } } @@ -883,11 +1125,11 @@ pub mod constants { } /// Copy WASM files from the `wasm` directory to every node's chain dir. -pub fn copy_wasm_to_chain_dir<'a>( +pub fn copy_wasm_to_chain_dir( working_dir: &Path, - chain_dir: &Path, + test_dir: &Path, chain_id: &ChainId, - genesis_validator_keys: impl Iterator, + // genesis_validator_keys: impl Iterator, ) { // Copy the built WASM files from "wasm" directory in the root of the // project. @@ -911,6 +1153,7 @@ pub fn copy_wasm_to_chain_dir<'a>( built_wasm_dir.to_string_lossy() ); } + let chain_dir = test_dir.join(chain_id.as_str()); let target_wasm_dir = chain_dir.join(config::DEFAULT_WASM_DIR); for file in &wasm_files { std::fs::copy( @@ -919,29 +1162,6 @@ pub fn copy_wasm_to_chain_dir<'a>( ) .unwrap(); } - - // Copy the built WASM files from "wasm" directory to each validator dir - for validator_name in genesis_validator_keys { - let target_wasm_dir = chain_dir - .join(utils::NET_ACCOUNTS_DIR) - .join(validator_name) - .join(config::DEFAULT_BASE_DIR) - .join(chain_id.as_str()) - .join(config::DEFAULT_WASM_DIR); - for file in &wasm_files { - let src = working_dir.join("wasm").join(file); - let dst = target_wasm_dir.join(file); - std::fs::copy(&src, &dst) - .wrap_err_with(|| { - format!( - "copying {} to {}", - &src.to_string_lossy(), - &dst.to_string_lossy(), - ) - }) - .unwrap(); - } - } } pub fn get_all_wasms_hashes( diff --git a/tests/src/integration/masp.rs b/tests/src/integration/masp.rs index f01b97c01f..c5b51a6eac 100644 --- a/tests/src/integration/masp.rs +++ b/tests/src/integration/masp.rs @@ -10,8 +10,9 @@ use test_log::test; use super::setup; use crate::e2e::setup::constants::{ AA_PAYMENT_ADDRESS, AA_VIEWING_KEY, AB_PAYMENT_ADDRESS, AB_VIEWING_KEY, - AC_PAYMENT_ADDRESS, AC_VIEWING_KEY, ALBERT, A_SPENDING_KEY, - BB_PAYMENT_ADDRESS, BERTHA, BTC, B_SPENDING_KEY, CHRISTEL, ETH, MASP, NAM, + AC_PAYMENT_ADDRESS, AC_VIEWING_KEY, ALBERT, ALBERT_KEY, A_SPENDING_KEY, + BB_PAYMENT_ADDRESS, BERTHA, BERTHA_KEY, BTC, B_SPENDING_KEY, CHRISTEL, + CHRISTEL_KEY, ETH, MASP, NAM, }; /// In this test we verify that users of the MASP receive the correct rewards @@ -28,7 +29,7 @@ fn masp_incentives() -> Result<()> { let (mut node, _services) = setup::setup()?; // Wait till epoch boundary node.next_epoch(); - // Send 20 BTC from Albert to PA + // Send 1 BTC from Albert to PA run( &node, Bin::Client, @@ -41,14 +42,14 @@ fn masp_incentives() -> Result<()> { "--token", BTC, "--amount", - "20", + "1", "--node", validator_one_rpc, ], )?; node.assert_success(); - // Assert BTC balance at VK(A) is 20 + // Assert BTC balance at VK(A) is 1 let captured = CapturedOutput::of(|| { run( &node, @@ -65,7 +66,7 @@ fn masp_incentives() -> Result<()> { ) }); assert!(captured.result.is_ok()); - assert!(captured.contains("btc: 20")); + assert!(captured.contains("btc: 1")); // Assert NAM balance at VK(A) is 0 let captured = CapturedOutput::of(|| { @@ -89,7 +90,7 @@ fn masp_incentives() -> Result<()> { // Wait till epoch boundary node.next_epoch(); - // Assert BTC balance at VK(A) is 20 + // Assert BTC balance at VK(A) is still 1 let captured = CapturedOutput::of(|| { run( &node, @@ -106,9 +107,9 @@ fn masp_incentives() -> Result<()> { ) }); assert!(captured.result.is_ok()); - assert!(captured.contains("btc: 20")); + assert!(captured.contains("btc: 1")); - // Assert NAM balance at VK(A) is 20*BTC_reward*(epoch_1-epoch_0) + // Assert NAM balance is a non-zero number (rewards have been dispensed) let captured = CapturedOutput::of(|| { run( &node, @@ -126,9 +127,10 @@ fn masp_incentives() -> Result<()> { }); assert!(captured.result.is_ok()); - assert!(captured.contains("nam: 22.74")); + assert!(captured.contains("nam: 0.022")); - // Assert NAM balance at MASP pool is 20*BTC_reward*(epoch_1-epoch_0) + // Assert NAM balance at MASP pool is exclusively the + // rewards from the shielded BTC let captured = CapturedOutput::of(|| { run( &node, @@ -145,12 +147,12 @@ fn masp_incentives() -> Result<()> { ) }); assert!(captured.result.is_ok()); - assert!(captured.contains("nam: 22.74")); + assert!(captured.contains("nam: 0.022")); // Wait till epoch boundary node.next_epoch(); - // Assert BTC balance at VK(A) is 20 + // Assert BTC balance at VK(A) is still 1 let captured = CapturedOutput::of(|| { run( &node, @@ -167,9 +169,10 @@ fn masp_incentives() -> Result<()> { ) }); assert!(captured.result.is_ok()); - assert!(captured.contains("btc: 20")); + assert!(captured.contains("btc: 1")); - // Assert NAM balance at VK(A) is 20*BTC_reward*(epoch_2-epoch_0) + // Assert NAM balance is a number greater than the last epoch's balance + // (more rewards have been dispensed) let captured = CapturedOutput::of(|| { run( &node, @@ -186,9 +189,10 @@ fn masp_incentives() -> Result<()> { ) }); assert!(captured.result.is_ok()); - assert!(captured.contains("nam: 90.96")); + assert!(captured.contains("nam: 0.08729")); - // Assert NAM balance at MASP pool is 20*BTC_reward*(epoch_2-epoch_0) + // Assert NAM balance at MASP pool is exclusively the + // rewards from the shielded BTC let captured = CapturedOutput::of(|| { run( &node, @@ -205,12 +209,12 @@ fn masp_incentives() -> Result<()> { ) }); assert!(captured.result.is_ok()); - assert!(captured.contains("nam: 90.96")); + assert!(captured.contains("nam: 0.08729")); // Wait till epoch boundary node.next_epoch(); - // Send 10 ETH from Albert to PA(B) + // Send 0.001 ETH from Albert to PA(B) run( &node, Bin::Client, @@ -223,14 +227,14 @@ fn masp_incentives() -> Result<()> { "--token", ETH, "--amount", - "10", + "0.001", "--node", validator_one_rpc, ], )?; node.assert_success(); - // Assert ETH balance at VK(B) is 10 + // Assert ETH balance at VK(B) is 0.001 let captured = CapturedOutput::of(|| { run( &node, @@ -247,7 +251,7 @@ fn masp_incentives() -> Result<()> { ) }); assert!(captured.result.is_ok()); - assert!(captured.contains("eth: 10")); + assert!(captured.contains("eth: 0.001")); // Assert NAM balance at VK(B) is 0 let captured = CapturedOutput::of(|| { @@ -271,7 +275,7 @@ fn masp_incentives() -> Result<()> { // Wait till epoch boundary node.next_epoch(); - // Assert ETH balance at VK(B) is 10 + // Assert ETH balance at VK(B) is still 0.001 let captured = CapturedOutput::of(|| { run( &node, @@ -288,9 +292,10 @@ fn masp_incentives() -> Result<()> { ) }); assert!(captured.result.is_ok()); - assert!(captured.contains("eth: 10")); + assert!(captured.contains("eth: 0.001")); - // Assert NAM balance at VK(B) is 10*ETH_reward*(epoch_4-epoch_3) + // Assert NAM balance at VK(B) is non-zero (rewards have been + // dispensed) let captured = CapturedOutput::of(|| { run( &node, @@ -307,10 +312,10 @@ fn masp_incentives() -> Result<()> { ) }); assert!(captured.result.is_ok()); - assert!(captured.contains("nam: 22.71432")); + assert!(captured.contains("nam: 0.0207")); - // Assert NAM balance at MASP pool is - // 20*BTC_reward*(epoch_4-epoch_0)+10*ETH_reward*(epoch_4-epoch_3) + // Assert NAM balance at MASP pool is an accumulation of + // rewards from both the shielded BTC and shielded ETH let captured = CapturedOutput::of(|| { run( &node, @@ -327,12 +332,12 @@ fn masp_incentives() -> Result<()> { ) }); assert!(captured.result.is_ok()); - assert!(captured.contains("nam: 386.46336")); + assert!(captured.contains("nam: 0.3726")); // Wait till epoch boundary node.next_epoch(); - // Send 10 ETH from SK(B) to Christel + // Send 0.001 ETH from SK(B) to Christel run( &node, Bin::Client, @@ -345,9 +350,9 @@ fn masp_incentives() -> Result<()> { "--token", ETH, "--amount", - "10", + "0.001", "--signing-keys", - BERTHA, + BERTHA_KEY, "--node", validator_one_rpc, ], @@ -375,7 +380,8 @@ fn masp_incentives() -> Result<()> { node.next_epoch(); - // Assert NAM balance at VK(B) is 10*ETH_reward*(ep-epoch_3) + // Assert VK(B) retains the NAM rewards dispensed in the correct + // amount. let captured = CapturedOutput::of(|| { run( &node, @@ -392,11 +398,12 @@ fn masp_incentives() -> Result<()> { ) }); assert!(captured.result.is_ok()); - assert!(captured.contains("nam: 86.60024")); + assert!(captured.contains("nam: 0.085204")); node.next_epoch(); + // Assert NAM balance at MASP pool is - // 20*BTC_reward*(epoch_5-epoch_0)+10*ETH_reward*(epoch_5-epoch_3) + // the accumulation of rewards from the shielded assets (BTC and ETH) let captured = CapturedOutput::of(|| { run( &node, @@ -413,12 +420,12 @@ fn masp_incentives() -> Result<()> { ) }); assert!(captured.result.is_ok()); - assert!(captured.contains("nam: 1180.41525")); + assert!(captured.contains("nam: 1.134567")); // Wait till epoch boundary node.next_epoch(); - // Send 20 BTC from SK(A) to Christel + // Send 1 BTC from SK(A) to Christel run( &node, Bin::Client, @@ -431,9 +438,9 @@ fn masp_incentives() -> Result<()> { "--token", BTC, "--amount", - "20", + "1", "--signing-keys", - ALBERT, + ALBERT_KEY, "--node", validator_one_rpc, ], @@ -459,7 +466,7 @@ fn masp_incentives() -> Result<()> { assert!(captured.result.is_ok()); assert!(captured.contains("No shielded btc balance found")); - // Assert NAM balance at VK(A) is 20*BTC_reward*(epoch_6-epoch_0) + // Assert VK(A) retained the NAM rewards let captured = CapturedOutput::of(|| { run( &node, @@ -476,10 +483,10 @@ fn masp_incentives() -> Result<()> { ) }); assert!(captured.result.is_ok()); - assert!(captured.contains("nam: 1407.16324")); + assert!(captured.contains("nam: 1.355211")); // Assert NAM balance at MASP pool is - // 20*BTC_reward*(epoch_6-epoch_0)+20*ETH_reward*(epoch_5-epoch_3) + // the accumulation of rewards from the shielded assets (BTC and ETH) let captured = CapturedOutput::of(|| { run( &node, @@ -496,12 +503,14 @@ fn masp_incentives() -> Result<()> { ) }); assert!(captured.result.is_ok()); - assert!(captured.contains("nam: 1520.37191")); + assert!(captured.contains("nam: 1.459458")); // Wait till epoch boundary node.next_epoch(); - // Assert NAM balance at VK(A) is 20*BTC_reward*(epoch_6-epoch_0) + // Assert NAM balance at VK(A) is the rewards dispensed earlier + // (since VK(A) has no shielded assets, no further rewards should + // be dispensed to that account) let captured = CapturedOutput::of(|| { run( &node, @@ -518,9 +527,11 @@ fn masp_incentives() -> Result<()> { ) }); assert!(captured.result.is_ok()); - assert!(captured.contains("nam: 1573.18")); + assert!(captured.contains("nam: 1.520493")); - // Assert NAM balance at VK(B) is 10*ETH_reward*(epoch_5-epoch_3) + // Assert NAM balance at VK(B) is the rewards dispensed earlier + // (since VK(A) has no shielded assets, no further rewards should + // be dispensed to that account) let captured = CapturedOutput::of(|| { run( &node, @@ -537,10 +548,10 @@ fn masp_incentives() -> Result<()> { ) }); assert!(captured.result.is_ok()); - assert!(captured.contains("nam: 126.565")); + assert!(captured.contains("nam: 0.125958")); // Assert NAM balance at MASP pool is - // 20*BTC_reward*(epoch_6-epoch_0)+10*ETH_reward*(epoch_5-epoch_3) + // the accumulation of rewards from the shielded assets (BTC and ETH) let captured = CapturedOutput::of(|| { run( &node, @@ -557,13 +568,13 @@ fn masp_incentives() -> Result<()> { ) }); assert!(captured.result.is_ok()); - assert!(captured.contains("nam: 1699.745")); + assert!(captured.contains("nam: 1.637454")); // Wait till epoch boundary to prevent conversion expiry during transaction // construction node.next_epoch(); - // Send 10*ETH_reward*(epoch_5-epoch_3) NAM from SK(B) to Christel + // Send all NAM rewards from SK(B) to Christel run( &node, Bin::Client, @@ -576,9 +587,9 @@ fn masp_incentives() -> Result<()> { "--token", NAM, "--amount", - "141.49967", + "0.137354", "--signing-keys", - BERTHA, + BERTHA_KEY, "--node", validator_one_rpc, ], @@ -588,7 +599,7 @@ fn masp_incentives() -> Result<()> { // Wait till epoch boundary node.next_epoch(); - // Send 20*BTC_reward*(epoch_6-epoch_0) NAM from SK(A) to Bertha + // Send all NAM rewards from SK(A) to Bertha run( &node, Bin::Client, @@ -601,33 +612,34 @@ fn masp_incentives() -> Result<()> { "--token", NAM, "--amount", - "1980.356", + "1.916208", "--signing-keys", - ALBERT, + ALBERT_KEY, "--node", validator_one_rpc, ], )?; node.assert_success(); - // Assert NAM balance at VK(A) is 0 - let captured = CapturedOutput::of(|| { - run( - &node, - Bin::Client, - vec![ - "balance", - "--owner", - AA_VIEWING_KEY, - "--token", - NAM, - "--node", - validator_one_rpc, - ], - ) - }); - assert!(captured.result.is_ok()); - assert!(captured.contains("No shielded nam balance found")); + // TODO: Fix once we can unsheild less than 10^-3 tokens + // // Assert NAM balance at VK(A) is 0 + // let captured = CapturedOutput::of(|| { + // run( + // &node, + // Bin::Client, + // vec![ + // "balance", + // "--owner", + // AA_VIEWING_KEY, + // "--token", + // NAM, + // "--node", + // validator_one_rpc, + // ], + // ) + // }); + // assert!(captured.result.is_ok()); + // assert!(captured.contains("No shielded nam balance found")); // Assert NAM balance at VK(B) is 0 let captured = CapturedOutput::of(|| { @@ -867,7 +879,7 @@ fn masp_txs_and_queries() -> Result<()> { "--amount", "10", "--gas-payer", - CHRISTEL, + CHRISTEL_KEY, "--node", validator_one_rpc, ], @@ -886,7 +898,7 @@ fn masp_txs_and_queries() -> Result<()> { "--amount", "15", "--gas-payer", - CHRISTEL, + CHRISTEL_KEY, "--node", validator_one_rpc, ], @@ -922,7 +934,7 @@ fn masp_txs_and_queries() -> Result<()> { "--amount", "10", "--gas-payer", - CHRISTEL, + CHRISTEL_KEY, "--node", validator_one_rpc, ], @@ -941,7 +953,7 @@ fn masp_txs_and_queries() -> Result<()> { "--amount", "7", "--gas-payer", - CHRISTEL, + CHRISTEL_KEY, "--node", validator_one_rpc, ], @@ -960,7 +972,7 @@ fn masp_txs_and_queries() -> Result<()> { "--amount", "7", "--gas-payer", - CHRISTEL, + CHRISTEL_KEY, "--node", validator_one_rpc, ], @@ -979,7 +991,7 @@ fn masp_txs_and_queries() -> Result<()> { "--amount", "7", "--gas-payer", - CHRISTEL, + CHRISTEL_KEY, "--node", validator_one_rpc, ], @@ -998,7 +1010,7 @@ fn masp_txs_and_queries() -> Result<()> { "--amount", "6", "--gas-payer", - CHRISTEL, + CHRISTEL_KEY, "--node", validator_one_rpc, ], @@ -1054,7 +1066,7 @@ fn masp_txs_and_queries() -> Result<()> { "--amount", "20", "--gas-payer", - CHRISTEL, + CHRISTEL_KEY, "--node", validator_one_rpc, ], diff --git a/tests/src/integration/setup.rs b/tests/src/integration/setup.rs index 9bbd00eef9..3a5739176d 100644 --- a/tests/src/integration/setup.rs +++ b/tests/src/integration/setup.rs @@ -5,9 +5,11 @@ use std::sync::{Arc, Mutex}; use color_eyre::eyre::{eyre, Result}; use namada_apps::cli::args; +use namada_apps::client::utils::PRE_GENESIS_DIR; use namada_apps::config; -use namada_apps::config::genesis::genesis_config; -use namada_apps::config::genesis::genesis_config::GenesisConfig; +use namada_apps::config::genesis::chain::Finalized; +use namada_apps::config::genesis::templates; +use namada_apps::config::genesis::templates::load_and_validate; use namada_apps::config::TendermintMode; use namada_apps::facade::tendermint::Timeout; use namada_apps::facade::tendermint_proto::google::protobuf::Timestamp; @@ -17,125 +19,147 @@ use namada_apps::node::ledger::shell::testing::node::{ }; use namada_apps::node::ledger::shell::testing::utils::TestDir; use namada_apps::node::ledger::shell::Shell; -use namada_core::types::address::nam; -use namada_core::types::chain::{ChainId, ChainIdPrefix}; -use toml::value::Table; +use namada_apps::wallet::pre_genesis; +use namada_core::types::chain::ChainIdPrefix; +use namada_sdk::wallet::alias::Alias; -use crate::e2e::setup::{ - copy_wasm_to_chain_dir, get_all_wasms_hashes, SINGLE_NODE_NET_GENESIS, -}; +use crate::e2e::setup::{copy_wasm_to_chain_dir, SINGLE_NODE_NET_GENESIS}; /// Env. var for keeping temporary files created by the integration tests const ENV_VAR_KEEP_TEMP: &str = "NAMADA_INT_KEEP_TEMP"; /// Setup a network with a single genesis validator node. pub fn setup() -> Result<(MockNode, MockServicesController)> { - initialize_genesis(|genesis| genesis) + initialize_genesis() } /// Setup folders with genesis, configs, wasm, etc. -pub fn initialize_genesis( - mut update_genesis: impl FnMut(GenesisConfig) -> GenesisConfig, -) -> Result<(MockNode, MockServicesController)> { +pub fn initialize_genesis() -> Result<(MockNode, MockServicesController)> { let working_dir = std::fs::canonicalize("..").unwrap(); let keep_temp = match std::env::var(ENV_VAR_KEEP_TEMP) { Ok(val) => val.to_ascii_lowercase() != "false", _ => false, }; let test_dir = TestDir::new(); + let template_dir = working_dir.join(SINGLE_NODE_NET_GENESIS); - // Open the source genesis file - let mut genesis = genesis_config::open_genesis_config( - working_dir.join(SINGLE_NODE_NET_GENESIS), - )?; + // Copy genesis files to test directory. + let templates = templates::All::read_toml_files(&template_dir) + .expect("Missing genesis files"); + let genesis_path = test_dir.path().join("int-test-genesis-src"); + std::fs::create_dir(&genesis_path) + .expect("Could not create test chain directory."); + templates + .write_toml_files(&genesis_path) + .expect("Could not write genesis files into test chain directory."); - genesis.parameters.vp_whitelist = - Some(get_all_wasms_hashes(&working_dir, Some("vp_"))); - genesis.parameters.tx_whitelist = - Some(get_all_wasms_hashes(&working_dir, Some("tx_"))); - - // Run the provided function on it - let genesis = update_genesis(genesis); + // Finalize the genesis config to derive the chain ID + let templates = load_and_validate(&template_dir) + .expect("Missing or invalid genesis files"); + let genesis_time = Default::default(); + let chain_id_prefix = ChainIdPrefix::from_str("integration-test").unwrap(); + let genesis = config::genesis::chain::finalize( + templates, + chain_id_prefix.clone(), + genesis_time, + Timeout::from_str("30s").unwrap(), + ); + let chain_id = &genesis.metadata.chain_id; // Run `init-network` to generate the finalized genesis config, keys and // addresses and update WASM checksums - let genesis_path = test_dir.path().join("e2e-test-genesis-src.toml"); - genesis_config::write_genesis_config(&genesis, &genesis_path); let wasm_checksums_path = working_dir.join("wasm/checksums.json"); - + let global_args = args::Global { + chain_id: Some(chain_id.clone()), + base_dir: test_dir.path().to_path_buf(), + wasm_dir: Some(test_dir.path().join(chain_id.as_str()).join("wasm")), + }; // setup genesis file namada_apps::client::utils::init_network( - args::Global { - chain_id: None, - base_dir: test_dir.path().to_path_buf(), - wasm_dir: None, - }, + global_args.clone(), args::InitNetwork { - genesis_path, + templates_path: genesis_path, wasm_checksums_path, - chain_id_prefix: ChainIdPrefix::from_str("integration-test") - .unwrap(), - unsafe_dont_encrypt: true, - consensus_timeout_commit: Timeout::from_str("1s").unwrap(), - localhost: true, - allow_duplicate_ip: true, + chain_id_prefix, + consensus_timeout_commit: Timeout::from_str("30s").unwrap(), dont_archive: true, archive_dir: None, + genesis_time, }, ); + let eth_bridge_params = genesis.get_eth_bridge_params(); let auto_drive_services = { // NB: for now, the only condition that // dictates whether mock services should // be enabled is if the Ethereum bridge // is enabled at genesis - genesis.ethereum_bridge_params.is_some() + eth_bridge_params.is_some() }; let enable_eth_oracle = { // NB: we only enable the oracle if the // Ethereum bridge is enabled at genesis - genesis.ethereum_bridge_params.is_some() + eth_bridge_params.is_some() }; let services_cfg = MockServicesCfg { auto_drive_services, enable_eth_oracle, }; - create_node(test_dir, &genesis, keep_temp, services_cfg) + finalize_wallet(&template_dir, &global_args, genesis); + create_node(test_dir, global_args, keep_temp, services_cfg) +} + +/// Add the address from the finalized genesis to the wallet. +/// Additionally add the validator keys to the wallet. +fn finalize_wallet( + template_dir: &Path, + global_args: &args::Global, + genesis: Finalized, +) { + let pre_genesis_path = template_dir.join("src").join(PRE_GENESIS_DIR); + let validator_alias_and_dir = + Some(("validator-0", pre_genesis_path.join("validator-0"))); + // Pre-load the validator pre-genesis wallet and its keys to validate that + // everything is in place + let validator_alias_and_pre_genesis_wallet = + validator_alias_and_dir.map(|(validator_alias, pre_genesis_dir)| { + ( + Alias::from(validator_alias), + pre_genesis::load(&pre_genesis_dir).unwrap_or_else(|err| { + panic!("Error loading validator pre-genesis wallet {err}") + }), + ) + }); + + // Try to load pre-genesis wallet + let pre_genesis_wallet = namada_apps::wallet::load(&pre_genesis_path); + let chain_dir = global_args + .base_dir + .join(global_args.chain_id.as_ref().unwrap().as_str()); + // Derive wallet from genesis + let wallet = genesis.derive_wallet( + &chain_dir, + pre_genesis_wallet, + validator_alias_and_pre_genesis_wallet, + ); + namada_apps::wallet::save(&wallet).unwrap(); } /// Create a mock ledger node. fn create_node( - base_dir: TestDir, - genesis: &GenesisConfig, + test_dir: TestDir, + global_args: args::Global, keep_temp: bool, services_cfg: MockServicesCfg, ) -> Result<(MockNode, MockServicesController)> { // look up the chain id from the global file. - let chain_id = if let toml::Value::String(chain_id) = - toml::from_str::( - &std::fs::read_to_string( - base_dir.path().join("global-config.toml"), - ) - .unwrap(), - ) - .unwrap() - .get("default_chain_id") - .unwrap() - { - chain_id.to_string() - } else { - return Err(eyre!("Could not read chain id from global-config.toml")); - }; + let chain_id = global_args.chain_id.unwrap_or_default(); - // the directory holding compiled wasm - let wasm_dir = base_dir.path().join(Path::new(&chain_id)).join("wasm"); // copy compiled wasms into the wasm directory - let chain_id = ChainId::from_str(&chain_id).unwrap(); copy_wasm_to_chain_dir( &std::fs::canonicalize("..").unwrap(), - &base_dir.path().join(Path::new(&chain_id.to_string())), + &global_args.base_dir, &chain_id, - genesis.validator.keys(), ); // instantiate and initialize the ledger node. @@ -148,19 +172,20 @@ fn create_node( let node = MockNode { shell: Arc::new(Mutex::new(Shell::new( config::Ledger::new( - base_dir.path(), + global_args.base_dir, chain_id.clone(), TendermintMode::Validator, ), - wasm_dir, + global_args + .wasm_dir + .expect("Wasm path not provided to integration test setup."), shell_handlers.tx_broadcaster, shell_handlers.eth_oracle_channels, None, 50 * 1024 * 1024, // 50 kiB 50 * 1024 * 1024, // 50 kiB - nam(), ))), - test_dir: ManuallyDrop::new(base_dir), + test_dir: ManuallyDrop::new(test_dir), keep_temp, services: Arc::new(services), results: Arc::new(Mutex::new(vec![])), diff --git a/tests/src/native_vp/eth_bridge_pool.rs b/tests/src/native_vp/eth_bridge_pool.rs index 3de46fc84b..ba69c6e380 100644 --- a/tests/src/native_vp/eth_bridge_pool.rs +++ b/tests/src/native_vp/eth_bridge_pool.rs @@ -18,7 +18,7 @@ mod test_bridge_pool_vp { use namada_apps::wallet::defaults::{albert_address, bertha_address}; use namada_apps::wasm_loader; use namada_sdk::eth_bridge::{ - wrapped_erc20s, Contracts, Erc20WhitelistEntry, EthereumBridgeConfig, + wrapped_erc20s, Contracts, Erc20WhitelistEntry, EthereumBridgeParams, UpgradeableContract, }; @@ -65,7 +65,7 @@ mod test_bridge_pool_vp { tx, ..Default::default() }; - let config = EthereumBridgeConfig { + let config = EthereumBridgeParams { erc20_whitelist: vec![Erc20WhitelistEntry { token_address: wnam(), token_cap: Amount::from_u64(TOKEN_CAP).native_denominated(), diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index 233d999861..1a3236ca40 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -96,7 +96,7 @@ //! - add rewards use namada::proof_of_stake::parameters::{OwnedPosParams, PosParams}; -use namada::proof_of_stake::test_init_genesis as init_genesis; +use namada::proof_of_stake::test_utils::test_init_genesis as init_genesis; use namada::proof_of_stake::types::GenesisValidator; use namada::types::storage::Epoch; diff --git a/tests/src/vm_host_env/ibc.rs b/tests/src/vm_host_env/ibc.rs index 583c58ada3..052b5b5e77 100644 --- a/tests/src/vm_host_env/ibc.rs +++ b/tests/src/vm_host_env/ibc.rs @@ -71,7 +71,7 @@ use namada::ledger::storage::mockdb::MockDB; use namada::ledger::storage::traits::Sha256Hasher; use namada::ledger::tx_env::TxEnv; use namada::ledger::{ibc, pos}; -use namada::proof_of_stake::parameters::PosParams; +use namada::proof_of_stake::OwnedPosParams; use namada::proto::Tx; use namada::tendermint::time::Time as TmTime; use namada::tendermint_proto::Protobuf as TmProtobuf; @@ -214,12 +214,13 @@ pub fn init_storage() -> (Address, Address) { ibc::init_genesis_storage(&mut env.wl_storage); let gov_params = GovernanceParameters::default(); gov_params.init_storage(&mut env.wl_storage).unwrap(); - pos::init_genesis_storage( + pos::test_utils::test_init_genesis( &mut env.wl_storage, - &PosParams::default(), + OwnedPosParams::default(), vec![get_dummy_genesis_validator()].into_iter(), Epoch(1), - ); + ) + .unwrap(); // store wasm code let key = Key::wasm_code(&code_hash); env.wl_storage.storage.write(&key, code.clone()).unwrap(); diff --git a/tx_prelude/src/proof_of_stake.rs b/tx_prelude/src/proof_of_stake.rs index 1f365f1b9c..2582287af2 100644 --- a/tx_prelude/src/proof_of_stake.rs +++ b/tx_prelude/src/proof_of_stake.rs @@ -25,7 +25,7 @@ impl Ctx { amount: token::Amount, ) -> TxResult { let current_epoch = self.get_block_epoch()?; - bond_tokens(self, source, validator, amount, current_epoch) + bond_tokens(self, source, validator, amount, current_epoch, None) } /// Unbond self-bonded tokens from a validator when `source` is `None` @@ -132,6 +132,7 @@ impl Ctx { current_epoch, commission_rate, max_commission_rate_change, + offset_opt: None, })?; Ok(validator_address) diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 29b37c5055..eda093d527 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -1005,6 +1005,15 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "copy_dir" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "543d1dd138ef086e2ff05e3a48cf9da045da2033d16f8538fd76b86cd49b2ca3" +dependencies = [ + "walkdir", +] + [[package]] name = "core-foundation" version = "0.9.3" @@ -3468,6 +3477,7 @@ dependencies = [ "chrono", "clap", "concat-idents", + "copy_dir", "derivative", "hyper", "lazy_static", diff --git a/wasm/checksums.json b/wasm/checksums.json index 873f8ae6a1..04ee8555d6 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,23 +1,23 @@ { - "tx_bond.wasm": "tx_bond.dbfe330a50d8d3511e125f611d88a3a32952de2077345ee79b5b320e9d11ece6.wasm", - "tx_bridge_pool.wasm": "tx_bridge_pool.ae875a5ddd27035b768678b2dbf70c8ecfa1bce316faee01b26bd0cbffb48b19.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.39b26c8ff0c06220163322c15a0f304232f5e3a6333c147d10d5a65e3d06c061.wasm", - "tx_ibc.wasm": "tx_ibc.d550ec9d20d965d91530ca4df74a75eeb27f88ae4484da3eef4b5599727bd94f.wasm", - "tx_init_account.wasm": "tx_init_account.06c8639a68cecf1160818bd92d81974a9990aceea08cbbcc47e18c51b2ec9449.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.daa176497bb1f4bb97346d1ba2387e0cc84e704eca70ef78a56228e03969fb76.wasm", - "tx_init_validator.wasm": "tx_init_validator.8d393d2cfdf55fd88beb4f26840d22e98da5e0390aac5fbcf1dc314e0eb4a9ca.wasm", - "tx_redelegate.wasm": "tx_redelegate.c586dc50a452948e2dd79718519bfa7b966999126ba52807bc4ba3a6f7a6290d.wasm", - "tx_resign_steward.wasm": "tx_resign_steward.994a9a3e10baacf900087382d2d8bb8045033927a3d040bf882c6d60ecfe0d5b.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.aceb2779c307f96e377e3e189589ea54e7b05d712cac27c6943e1d941334b421.wasm", - "tx_transfer.wasm": "tx_transfer.adab8a00709de083b6b4b534d4dfe002d479085ce1a766718dd3afe920659ac3.wasm", - "tx_unbond.wasm": "tx_unbond.a1dc615400ad625cf9b82ea28735a2fbffd1dd6745761eff6df4d08358162195.wasm", - "tx_unjail_validator.wasm": "tx_unjail_validator.9debbc06681dd602e8a34ccf20d50dddee9963447409d2597893bf54937e7cee.wasm", - "tx_update_account.wasm": "tx_update_account.3917c5c25d2ffb0d717b9475a74d825840db94d48fcb4a0fe7450f3b32258cbe.wasm", - "tx_update_steward_commission.wasm": "tx_update_steward_commission.1a6f68eaf3b2eff9da777ce8c73f94865c49fa2ef94b5c0e1d65f46960cc9a5e.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.3f818de8c638e24249a26ccd20ec6a1700092c3700dcf6b91aea11058586ebef.wasm", - "tx_withdraw.wasm": "tx_withdraw.3cf7e6b6b77abd0eae58302659c9de31919894f36c0f7dbd85a6c853cd023483.wasm", - "vp_implicit.wasm": "vp_implicit.46d4206719acaaa8d0a422b6f969719b458cc5ca7c25677eb4bf23b52b4b6490.wasm", - "vp_masp.wasm": "vp_masp.c1e8f569ed3ad2bf5152e34f8ea9e255439066e0800ae73620a7be9a976f2ff7.wasm", - "vp_user.wasm": "vp_user.6b5bf2c3a5367d4f379187a8d2cf71457085aa1184a2b98b9e00ab6978847e08.wasm", - "vp_validator.wasm": "vp_validator.a683a9724e335cdf8f33eaee9ac3127a21d9f76e9769a5f3aea63b3bf04f2011.wasm" -} \ No newline at end of file + "tx_bond.wasm": "tx_bond.9fdaf63c3052a44195c8280d749e1682654ee2bad6edd25c6551bdc8197f7512.wasm", + "tx_bridge_pool.wasm": "tx_bridge_pool.605e9b7e8b0ff27863567b56d3f5e757783f5c75c596260d7c0805a47b7a877e.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.f036ef369e754cb6c75453f9b380f95be588d330f993284b08b477231d1ed0eb.wasm", + "tx_ibc.wasm": "tx_ibc.95832c0d92bb83ac2cde7b3c7cde9edae7c21f8dd5b32d93a9368093d58b5f15.wasm", + "tx_init_account.wasm": "tx_init_account.f525992552827cfd7693d568879dfd1f54406af9906b8827bc86e4beb21a0efe.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.9c3cf15da2b14e2c9898f5910cbfcffb80ef14dfcd0082dc9039b170cb3cc208.wasm", + "tx_init_validator.wasm": "tx_init_validator.1b695e5869532bbaebe5d99d48a267ae0891e9c4470abbbad5d0b37118e35ca6.wasm", + "tx_redelegate.wasm": "tx_redelegate.63f6c6b2f1582b357870ca9b779e88139c1e859b18fe7ada71dfdf5842fd83c7.wasm", + "tx_resign_steward.wasm": "tx_resign_steward.c21e812ba4a095d4b5eca562a94a2ef5071522a95f87a641c5f6e2bb2a4c890d.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.e5b1b047b44cde0cc8f9f89ba15f611dbefe8a3c060c4012363fdf07e92c6875.wasm", + "tx_transfer.wasm": "tx_transfer.c34a0a0b15ae448db7bc455bab8c59e6510c5fe9cb2d6386897030c37a189e42.wasm", + "tx_unbond.wasm": "tx_unbond.e846fd512237bdee232edc8d87e637ab3631942e3e1eda635d595e96d52bf489.wasm", + "tx_unjail_validator.wasm": "tx_unjail_validator.1c8db6285b60d8ce80ce5c1ff7d43755e50cd3749fad5fb286f66592112ba4cd.wasm", + "tx_update_account.wasm": "tx_update_account.9f9493f2fb39d0a5f0fa3327a78828e387ba15690ad86e96d4b8cdf509cf1efc.wasm", + "tx_update_steward_commission.wasm": "tx_update_steward_commission.92bcf758cbb58be93ae5d7fde616a3974016f0d41c09b7333bc6041bc23e8d30.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.825ffa0c7c7c588cb668af4ff6ba3ce5c5d1c37c0728c18a7eb807a02da52158.wasm", + "tx_withdraw.wasm": "tx_withdraw.0225f3c8ac6e4f2d4636568048035156c936d9a8bc12d4c0d619598089af6f47.wasm", + "vp_implicit.wasm": "vp_implicit.e8dd924d43f11854724350c3e082a35898dc03e21f1543c117cff6763d881d2b.wasm", + "vp_masp.wasm": "vp_masp.64553be3434516e8de6b3e0a445582dce7d7975bd9765f4fbd9875bdbf0f9feb.wasm", + "vp_user.wasm": "vp_user.37fee9afa11b5899c824b6b8e1f0a626f5fd05c9813af454081a797772ca3de6.wasm", + "vp_validator.wasm": "vp_validator.e6283c764480d04942c0fa2fb3d6e3d1cd18565b235b09e6a6deb7918c808f31.wasm" +} diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index f738c90527..974079380c 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -1005,6 +1005,15 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "copy_dir" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "543d1dd138ef086e2ff05e3a48cf9da045da2033d16f8538fd76b86cd49b2ca3" +dependencies = [ + "walkdir", +] + [[package]] name = "core-foundation" version = "0.9.3" @@ -3468,6 +3477,7 @@ dependencies = [ "chrono", "clap", "concat-idents", + "copy_dir", "derivative", "hyper", "lazy_static",