From cfd2320a97c20e749662f1f964b1cb318a8e486b Mon Sep 17 00:00:00 2001 From: Greg Nazario Date: Wed, 4 May 2022 12:32:45 -0700 Subject: [PATCH] [aptos-cli] Build genesis transaction --- Cargo.lock | 1 + crates/aptos/Cargo.toml | 1 + crates/aptos/src/genesis/config.rs | 76 ++++++++++++++++++++++++++++-- crates/aptos/src/genesis/git.rs | 54 +++++++++++++++++++-- crates/aptos/src/genesis/keys.rs | 7 +-- crates/aptos/src/genesis/mod.rs | 53 ++++++++++++++++----- 6 files changed, 169 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2636ec15f8fda..e6e827f99ca60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -197,6 +197,7 @@ dependencies = [ "tokio-util 0.6.9", "toml", "uuid", + "vm-genesis", ] [[package]] diff --git a/crates/aptos/Cargo.toml b/crates/aptos/Cargo.toml index 353bea176fb4a..6415952e1af0c 100644 --- a/crates/aptos/Cargo.toml +++ b/crates/aptos/Cargo.toml @@ -57,6 +57,7 @@ move-stdlib = { git = "https://github.com/move-language/move", rev = "f2e7585b1e move-vm-runtime = { git = "https://github.com/move-language/move", rev = "f2e7585b1ed5bd2810163d6bdebafe5a388881d3", features=["testing"] } framework = { path = '../../aptos-move/framework' } move-table-extension = { git = "https://github.com/move-language/move", rev = "f2e7585b1ed5bd2810163d6bdebafe5a388881d3" } +vm-genesis = { path = "../../aptos-move/vm-genesis" } [build-dependencies] shadow-rs = "0.11.0" diff --git a/crates/aptos/src/genesis/config.rs b/crates/aptos/src/genesis/config.rs index 4d0da8dc0f605..a13bed93c2cc3 100644 --- a/crates/aptos/src/genesis/config.rs +++ b/crates/aptos/src/genesis/config.rs @@ -5,10 +5,23 @@ use crate::{ common::types::{CliError, CliTypedResult}, genesis::git::from_yaml, }; +use aptos_config::config::HANDSHAKE_VERSION; use aptos_crypto::{ed25519::Ed25519PublicKey, x25519}; -use aptos_types::{chain_id::ChainId, network_address::DnsName}; +use aptos_types::{ + chain_id::ChainId, + network_address::{DnsName, NetworkAddress, Protocol}, + transaction::authenticator::AuthenticationKey, +}; use serde::{Deserialize, Serialize}; -use std::{fs::File, io::Read, path::PathBuf, str::FromStr}; +use std::{ + convert::TryFrom, + fs::File, + io::Read, + net::{Ipv4Addr, Ipv6Addr}, + path::PathBuf, + str::FromStr, +}; +use vm_genesis::Validator; /// Template for setting up Github for Genesis /// @@ -39,7 +52,7 @@ impl Layout { /// A set of configuration needed to add a Validator to genesis /// -#[derive(Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct ValidatorConfiguration { /// Key used for signing in consensus pub consensus_key: Ed25519PublicKey, @@ -53,13 +66,68 @@ pub struct ValidatorConfiguration { pub full_node_host: Option, } +impl From for Validator { + fn from(config: ValidatorConfiguration) -> Self { + let auth_key = AuthenticationKey::ed25519(&config.account_key); + let validator_addresses = vec![config + .validator_host + .as_network_address(config.network_key) + .unwrap()]; + let full_node_addresses = if let Some(full_node_host) = config.full_node_host { + vec![full_node_host + .as_network_address(config.network_key) + .unwrap()] + } else { + vec![] + }; + + Validator { + address: auth_key.derived_address(), + name: vec![], // TODO: To remove + consensus_pubkey: bcs::to_bytes(&config.consensus_key).unwrap(), + operator_address: auth_key.derived_address(), + operator_name: vec![], // TODO: To remove + network_address: bcs::to_bytes(&validator_addresses).unwrap(), + full_node_network_address: bcs::to_bytes(&full_node_addresses).unwrap(), + operator_auth_key: auth_key, + auth_key, + } + } +} + /// Combined Host (DnsName or IP) and port -#[derive(Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct HostAndPort { pub host: DnsName, pub port: u16, } +impl HostAndPort { + pub fn as_network_address(&self, key: x25519::PublicKey) -> CliTypedResult { + let host = self.host.to_string(); + + // Since DnsName supports IPs as well, let's properly fix what the type is + let host_protocol = if let Ok(ip) = Ipv4Addr::from_str(&host) { + Protocol::Ip4(ip) + } else if let Ok(ip) = Ipv6Addr::from_str(&host) { + Protocol::Ip6(ip) + } else { + Protocol::Dns(self.host.clone()) + }; + let port_protocol = Protocol::Tcp(self.port); + let noise_protocol = Protocol::NoiseIK(key); + let handshake_protocol = Protocol::Handshake(HANDSHAKE_VERSION); + + NetworkAddress::try_from(vec![ + host_protocol, + port_protocol, + noise_protocol, + handshake_protocol, + ]) + .map_err(|e| CliError::UnexpectedError(e.to_string())) + } +} + impl FromStr for HostAndPort { type Err = CliError; diff --git a/crates/aptos/src/genesis/git.rs b/crates/aptos/src/genesis/git.rs index 3efab3c247bd5..7aa5daa09b345 100644 --- a/crates/aptos/src/genesis/git.rs +++ b/crates/aptos/src/genesis/git.rs @@ -142,7 +142,7 @@ impl GitClient { match self { GitClient::Local(local_repository_path) => { let path = local_repository_path.join(format!("{}.yml", name)); - let mut file = std::fs::File::open(path.clone()) + let mut file = std::fs::File::open(path.as_path()) .map_err(|e| CliError::IO(path.display().to_string(), e))?; let mut contents = String::new(); file.read_to_string(&mut contents) @@ -161,10 +161,10 @@ impl GitClient { GitClient::Local(local_repository_path) => { let path = local_repository_path.join(format!("{}.yml", name)); let mut file = if path.exists() { - std::fs::File::open(path.clone()) + std::fs::File::open(path.as_path()) .map_err(|e| CliError::IO(path.display().to_string(), e))? } else { - std::fs::File::create(path.clone()) + std::fs::File::create(path.as_path()) .map_err(|e| CliError::IO(path.display().to_string(), e))? }; @@ -186,7 +186,7 @@ impl GitClient { if path.exists() && path.is_dir() { // Do nothing } else { - std::fs::create_dir(path.clone()) + std::fs::create_dir(path.as_path()) .map_err(|e| CliError::IO(path.display().to_string(), e))? }; } @@ -197,6 +197,52 @@ impl GitClient { Ok(()) } + + /// Retrieve bytecode Move modules from a module folder + pub fn get_modules(&self, name: &str) -> CliTypedResult>> { + let mut modules = Vec::new(); + + match self { + GitClient::Local(local_repository_path) => { + let module_folder = local_repository_path.join(name); + if !module_folder.is_dir() { + return Err(CliError::UnexpectedError(format!( + "{} is not a directory!", + module_folder.display() + ))); + } + + let files = std::fs::read_dir(module_folder.as_path()) + .map_err(|e| CliError::IO(module_folder.display().to_string(), e))?; + + for maybe_file in files { + let file = maybe_file + .map_err(|e| CliError::UnexpectedError(e.to_string()))? + .path(); + let extension = file.extension(); + + // Only collect move files + if file.is_file() && extension.is_some() && extension.unwrap() == "mv" { + modules.push( + std::fs::read(file.as_path()) + .map_err(|e| CliError::IO(file.display().to_string(), e))?, + ); + } + } + } + GitClient::Github(client) => { + let files = client.get_directory(name)?; + + for file in files { + // Only collect .mv files + if file.ends_with(".mv") { + modules.push(base64::decode(client.get_file(&file)?)?) + } + } + } + } + Ok(modules) + } } pub fn to_yaml(input: &T) -> CliTypedResult { diff --git a/crates/aptos/src/genesis/keys.rs b/crates/aptos/src/genesis/keys.rs index b0d73d7978da5..6c904831b640e 100644 --- a/crates/aptos/src/genesis/keys.rs +++ b/crates/aptos/src/genesis/keys.rs @@ -22,7 +22,8 @@ const NETWORK_KEY_FILE: &str = "network.key"; /// Generate account key, consensus key, and network key for a validator #[derive(Parser)] pub struct GenerateKeys { - #[clap(long, parse(from_os_str))] + /// Output path for the three keys + #[clap(long, parse(from_os_str), default_value = ".")] output_path: PathBuf, } @@ -43,7 +44,7 @@ impl CliCommand> for GenerateKeys { } } -/// Upload ValidatorCredentials +/// Set ValidatorConfiguration for a single validator in the git repository #[derive(Parser)] pub struct SetValidatorConfiguration { /// Username @@ -52,7 +53,7 @@ pub struct SetValidatorConfiguration { #[clap(flatten)] git_options: GitOptions, /// Path to credentials - #[clap(long, parse(from_os_str), default_value = ".aptos/")] + #[clap(long, parse(from_os_str), default_value = ".")] credentials_path: PathBuf, /// Host and port pair for the validator e.g. 127.0.0.1:6180 #[clap(long)] diff --git a/crates/aptos/src/genesis/mod.rs b/crates/aptos/src/genesis/mod.rs index 4064fe221d4fb..127c529bc0631 100644 --- a/crates/aptos/src/genesis/mod.rs +++ b/crates/aptos/src/genesis/mod.rs @@ -14,10 +14,15 @@ use crate::{ CliCommand, CliResult, }; use aptos_crypto::ed25519::Ed25519PublicKey; -use aptos_types::chain_id::ChainId; +use aptos_types::{ + chain_id::ChainId, + on_chain_config::{ConsensusConfigV2, OnChainConsensusConfig, VMPublishingOption}, +}; use async_trait::async_trait; use clap::Parser; -use serde::Serialize; +use vm_genesis::Validator; + +const MIN_PRICE_PER_GAS_UNIT: u64 = 1; /// Tool for setting up and building the Genesis transaction /// @@ -40,7 +45,7 @@ impl GenesisTool { } } -/// Generate genesis from a git repo +/// Generate genesis from a git repository #[derive(Parser)] pub struct GenerateGenesis { #[clap(flatten)] @@ -48,36 +53,60 @@ pub struct GenerateGenesis { } #[async_trait] -impl CliCommand for GenerateGenesis { +impl CliCommand<()> for GenerateGenesis { fn command_name(&self) -> &'static str { "GenerateGenesis" } - async fn execute(self) -> CliTypedResult { - // TODO: Generate genesis, this right now just reads all users - fetch_genesis_info(self.github_options) + async fn execute(self) -> CliTypedResult<()> { + let genesis_info = fetch_genesis_info(self.github_options)?; + + let consensus_config = OnChainConsensusConfig::V2(ConsensusConfigV2 { + two_chain: true, + decoupled_execution: true, + back_pressure_limit: 10, + exclude_round: 20, + }); + + vm_genesis::encode_genesis_transaction( + genesis_info.root_key.clone(), + &genesis_info.validators, + &genesis_info.modules, + Some(VMPublishingOption::open()), // TODO: Remove + consensus_config, // TODO: Remove + genesis_info.chain_id, + MIN_PRICE_PER_GAS_UNIT, + ); + + Ok(()) } } +/// Retrieves all information for genesis from the Git repository pub fn fetch_genesis_info(git_options: GitOptions) -> CliTypedResult { let client = git_options.get_client()?; let layout: Layout = client.get(LAYOUT_NAME)?; - let mut configs = Vec::new(); + let mut validators = Vec::new(); for user in &layout.users { - configs.push(client.get(user)?); + validators.push(client.get::(user)?.into()); } + let modules = client.get_modules(&layout.modules_folder)?; + Ok(GenesisInfo { chain_id: layout.chain_id, root_key: layout.root_key, - participants: configs, + validators, + modules, }) } -#[derive(Debug, Serialize)] +/// Holder object for all pieces needed to generate a genesis transaction +#[derive(Clone)] pub struct GenesisInfo { chain_id: ChainId, root_key: Ed25519PublicKey, - participants: Vec, + validators: Vec, + modules: Vec>, }