From cf42994b301b5e1a716ed5152941e5701ff6e376 Mon Sep 17 00:00:00 2001 From: codilion Date: Fri, 16 Aug 2024 16:53:13 +0200 Subject: [PATCH] [tools] cli init includes OL pledges (#307) Co-authored-by: 0o-de-lally <1364012+0o-de-lally@users.noreply.github.com> --- tools/config/src/config_cli.rs | 52 +++++--- .../{legacy_config.rs => config_wizard.rs} | 14 +- tools/config/src/lib.rs | 2 +- tools/config/src/make_yaml_public_fullnode.rs | 10 +- tools/config/src/validator_config.rs | 18 ++- tools/genesis/src/wizard.rs | 2 +- types/src/core_types/app_cfg.rs | 124 +++++++++++------- .../core_types/{dialogue.rs => dialogue.depr} | 43 +----- types/src/core_types/mod.rs | 1 + types/src/core_types/pledge.rs | 120 +++++++++++++++++ types/src/ol_progress.rs | 27 +++- 11 files changed, 287 insertions(+), 126 deletions(-) rename tools/config/src/{legacy_config.rs => config_wizard.rs} (86%) rename types/src/core_types/{dialogue.rs => dialogue.depr} (74%) create mode 100644 types/src/core_types/pledge.rs diff --git a/tools/config/src/config_cli.rs b/tools/config/src/config_cli.rs index de2701722..17c1e7325 100644 --- a/tools/config/src/config_cli.rs +++ b/tools/config/src/config_cli.rs @@ -1,9 +1,9 @@ use crate::{ - legacy_config, + config_wizard, make_yaml_public_fullnode::{download_genesis, get_genesis_waypoint, init_fullnode_yaml}, validator_config::{validator_dialogue, vfn_dialogue}, }; -use anyhow::{Context, Result}; +use anyhow::{anyhow, Context, Result}; use clap::Parser; use libra_types::{ core_types::app_cfg::{self, AppCfg}, @@ -34,7 +34,7 @@ pub struct ConfigCli { #[derive(clap::Subcommand)] enum ConfigSub { - /// Generates a libra-cli-config.yaml for cli tools like txs, tower, etc. Note: the file can also be used for Carpe, though that app uses a different default directory than these cli tools. + /// Generates a libra-cli-config.yaml for cli tools like `txs`, `query`, etc. Note: the file can also be used for Carpe, though that app uses a different default directory than these cli tools. Init { /// force an account address instead of reading from mnemonic, requires --force_authkey #[clap(long)] @@ -49,13 +49,10 @@ enum ConfigSub { #[clap(long)] playlist_url: Option, }, - // TODO: add WhoAmI to show libra-cli-config.yaml profile info. - /// Utils for libra-cli-config.yaml file - #[clap(arg_required_else_help(true))] Fix { /// optional, reset the address from mnemonic. Will also lookup on the chain for the actual address if you forgot it, or rotated your authkey. - #[clap(short, long)] - address: bool, + #[clap(short('a'), long)] + reset_address: bool, #[clap(short, long)] remove_profile: Option, @@ -67,7 +64,7 @@ enum ConfigSub { /// Show the addresses and configs on this device View {}, - // COMMIT NOTE: we havent'used vendor tooling configs for anything. + // COMMIT NOTE: we haven't used vendor tooling configs for anything. /// Generate validators' config file ValidatorInit { // just make the VFN file @@ -88,16 +85,25 @@ impl ConfigCli { pub async fn run(&self) -> Result<()> { match &self.subcommand { Some(ConfigSub::Fix { - address, + reset_address, remove_profile, force_url, }) => { // Load configuration file - let mut cfg = AppCfg::load(self.path.clone())?; + let mut cfg = AppCfg::load(self.path.clone()) + .map_err(|e| anyhow!("no config file found for libra tools, {}", e))?; + if !cfg.user_profiles.is_empty() { + println!("your profiles:"); + for p in &cfg.user_profiles { + println!("- address: {}, nickname: {}", p.account, p.nickname); + } + } else { + println!("no profiles found"); + } // Handle address fix option - if *address { - let mut account_keys = legacy_config::prompt_for_account()?; + let profile = if *reset_address { + let mut account_keys = config_wizard::prompt_for_account()?; let client = Client::new(cfg.pick_url(self.chain_name)?); @@ -120,6 +126,9 @@ impl ConfigCli { let profile = app_cfg::Profile::new(account_keys.auth_key, account_keys.account); + // Add profile to configuration + cfg.maybe_add_profile(profile)?; + // Prompt to set as default profile if dialoguer::Confirm::new() .with_prompt("set as default profile?") @@ -129,9 +138,18 @@ impl ConfigCli { .set_default(account_keys.account.to_hex_literal()); } - // Add profile to configuration - cfg.maybe_add_profile(profile)?; - } + cfg.get_profile_mut(Some(account_keys.account.to_hex_literal())) + } else { + // get default profile + println!("will try to fix your default profile"); + cfg.get_profile_mut(None) + }?; + + println!("using profile: {}", &profile.nickname); + + // user can take pledge here on fix or on init + profile.maybe_offer_basic_pledge(); + profile.maybe_offer_validator_pledge(); // Remove profile if specified if let Some(p) = remove_profile { @@ -160,7 +178,7 @@ impl ConfigCli { test_private_key, playlist_url, }) => { - legacy_config::wizard( + config_wizard::wizard( force_authkey.to_owned(), force_address.to_owned(), self.path.to_owned(), diff --git a/tools/config/src/legacy_config.rs b/tools/config/src/config_wizard.rs similarity index 86% rename from tools/config/src/legacy_config.rs rename to tools/config/src/config_wizard.rs index f2c0eb222..8f342a1c2 100644 --- a/tools/config/src/legacy_config.rs +++ b/tools/config/src/config_wizard.rs @@ -4,6 +4,7 @@ use diem_types::chain_id::NamedChain; use libra_types::{ core_types::{app_cfg::AppCfg, network_playlist::NetworkPlaylist}, exports::{AccountAddress, AuthenticationKey, Client}, + ol_progress, type_extensions::client_ext::ClientExt, }; use libra_wallet::account_keys::{get_ol_legacy_address, AccountKeys}; @@ -33,6 +34,8 @@ pub async fn wizard( (account_keys.auth_key, account_keys.account) }; + let spin = ol_progress::OLProgress::spin_steady(250, "fetching metadata".to_string()); + // if the user specified both a chain name and playlist, then the playlist will override the default settings for the named chain. let mut np = match network_playlist { Some(a) => a, @@ -60,14 +63,21 @@ pub async fn wizard( }; } - let cfg = AppCfg::init_app_configs(authkey, address, config_dir, chain_name, Some(np))?; + let mut cfg = AppCfg::init_app_configs(authkey, address, config_dir, chain_name, Some(np))?; + + spin.finish(); + // offer both pledges on init + let profile = cfg.get_profile_mut(None)?; + profile.maybe_offer_basic_pledge(); let p = cfg.save_file().context(format!( "could not initialize configs at {}", cfg.workspace.node_home.to_str().unwrap() ))?; - println!("Success, config saved to {}", p.to_str().unwrap()); + ol_progress::OLProgress::make_fun(); + println!("config saved to {}", p.display()); + ol_progress::OLProgress::complete("SUCCESS: libra tool configured"); Ok(cfg) } diff --git a/tools/config/src/lib.rs b/tools/config/src/lib.rs index 26d5125e2..0e73bfd79 100644 --- a/tools/config/src/lib.rs +++ b/tools/config/src/lib.rs @@ -1,5 +1,5 @@ pub mod config_cli; -pub mod legacy_config; +pub mod config_wizard; pub mod make_profile; // TODO: deprecated? pub mod make_yaml_public_fullnode; pub mod make_yaml_validator; diff --git a/tools/config/src/make_yaml_public_fullnode.rs b/tools/config/src/make_yaml_public_fullnode.rs index 0ecd48389..eb5b60360 100644 --- a/tools/config/src/make_yaml_public_fullnode.rs +++ b/tools/config/src/make_yaml_public_fullnode.rs @@ -12,9 +12,9 @@ use std::{ path::{Path, PathBuf}, }; -const FN_FILENAME: &str = "fullnode.yaml"; -const VFN_FILENAME: &str = "vfn.yaml"; -const DEFAULT_WAYPOINT_VERSION: &str = "6.9.0"; +pub const FN_FILENAME: &str = "fullnode.yaml"; +pub const VFN_FILENAME: &str = "vfn.yaml"; +pub const GENESIS_FILES_VERSION: &str = "7.0.0"; #[derive(Debug, Deserialize)] #[allow(dead_code)] struct GithubContent { @@ -201,7 +201,7 @@ pub async fn download_genesis(home_dir: Option) -> anyhow::Result<()> { let latest_path = format!( "{}/v{}/genesis.blob", "https://raw.githubusercontent.com/0LNetworkCommunity/epoch-archive-mainnet/main/upgrades", - latest_version.unwrap_or(DEFAULT_WAYPOINT_VERSION) + latest_version.unwrap_or(GENESIS_FILES_VERSION) ); // Fetch the latest waypoint @@ -244,7 +244,7 @@ pub async fn get_genesis_waypoint(home_dir: Option) -> anyhow::Result, /// Network profile pub network_playlist: Vec, @@ -144,42 +146,7 @@ impl AppCfg { Ok(toml_path) } - pub fn migrate(legacy_file: Option, output: Option) -> anyhow::Result { - let l = LegacyToml::parse_toml(legacy_file)?; - - let nodes = if let Some(v) = l.profile.upstream_nodes.as_ref() { - v.iter() - .map(|u| HostProfile { - url: u.to_owned(), - note: u.to_string(), - ..Default::default() - }) - .collect::>() - } else { - vec![] - }; - let np = NetworkPlaylist { - chain_name: l.chain_info.chain_id, - nodes, - }; - let app_cfg = AppCfg { - workspace: l.workspace, - user_profiles: vec![l.profile], - network_playlist: vec![np], - tx_configs: l.tx_configs, - }; - - if let Some(p) = output { - fs::create_dir_all(&p)?; - println!("created file for {}", p.to_str().unwrap()); - let yaml = serde_yaml::to_string(&app_cfg)?; - fs::write(p, yaml.as_bytes())?; - } else { - app_cfg.save_file()?; - } - - Ok(app_cfg) - } + // commit note: cleanup deprecated /// Get where the block/proofs are stored. pub fn get_block_dir(&self, nickname: Option) -> anyhow::Result { @@ -223,6 +190,10 @@ impl AppCfg { pub fn get_profile(&self, nickname: Option) -> anyhow::Result<&Profile> { let idx = self.get_profile_idx(nickname).unwrap_or(0); let p = self.user_profiles.get(idx).context("no profile at index")?; + // The privilege to use this software depends on the user upholding a code of conduct and taking the pledge. Totally cool if you don't want to, but you'll need to write your own tools. + if !p.check_has_pledge(0) { + println!("user profile has not taken 'Protect the Game' pledge, exiting."); + } Ok(p) } @@ -429,12 +400,15 @@ pub struct Profile { /// An opportunity for the Miner to write a message on their genesis block. pub statement: String, + /// Pledges the user took + pub pledges: Option>, // NOTE: V7: deprecated // Deprecation: : /// ip address of this node. May be different from transaction URL. // pub ip: Ipv4Addr, + // V7.0.3 deprecated // Deprecation: /// Other nodes to connect for fallback connections - pub upstream_nodes: Option>, + // pub upstream_nodes: Option>, } impl Default for Profile { @@ -451,7 +425,7 @@ impl Default for Profile { nickname: "default".to_string(), on_chain: false, balance: SlowWalletBalance::default(), - upstream_nodes: None, // Note: deprecated, here for migration + pledges: None, } } } @@ -478,12 +452,63 @@ impl Profile { .context("no private key found")?; Ok(key) } + + // push a pledge + pub fn push_pledge(&mut self, new: Pledge) { + if let Some(list) = &mut self.pledges { + let found = list.iter().find(|e| e.id == new.id); + if found.is_none() { + list.push(new); + } else { + println!("pledge '{}' already found on this account", &new.question); + } + } else { + self.pledges = Some(vec![new]) + } + } + + // check protect game pledge + pub fn check_has_pledge(&self, pledge_id: u8) -> bool { + // are we in CI? + if *MODE_0L != NamedChain::MAINNET { + return true; + }; + + if let Some(list) = &self.pledges { + // check the pledge exists + return list + .iter() + .any(|e| e.id == 0 && Pledge::check_pledge_hash(pledge_id, &e.hash)); + } + + false + } + + // offer pledge if none + pub fn maybe_offer_basic_pledge(&mut self) { + if !self.check_has_pledge(0) { + let p = Pledge::pledge_protect_the_game(); + if p.pledge_dialogue() { + self.push_pledge(p) + } + } + } + + // offer validator pledge + pub fn maybe_offer_validator_pledge(&mut self) { + if !self.check_has_pledge(1) { + let p = Pledge::pledge_validator(); + if p.pledge_dialogue() { + self.push_pledge(p) + } + } + } } pub fn get_nickname(acc: AccountAddress) -> String { - // let's check if this is a legacy/founder key, it will have 16 zeros at the start, and that's not a useful nickname - if acc.to_string()[..32] == *"00000000000000000000000000000000" { - return acc.to_string()[33..37].to_owned(); + // let's check if this is a legacy/founder key, it will have 32 zeros at the start, and that's not a useful nickname + if acc.to_string()[..31] == *"00000000000000000000000000000000" { + return acc.to_string()[32..36].to_owned(); } acc.to_string()[..4].to_owned() @@ -521,7 +546,7 @@ pub struct TxConfigs { /// Miner transactions cost // #[serde(default = "TxCost::default_miner_txs_cost")] pub miner_txs_cost: Option, - /// Cheap or test transation costs + /// Cheap or test transaction costs // #[serde(default = "TxCost::default_cheap_txs_cost")] pub cheap_txs_cost: Option, } @@ -565,7 +590,7 @@ impl TxCost { max_gas_unit_for_tx: units, // oracle upgrade transaction is expensive. // TODO: the GAS_UNIT_PRICE is set in DIEM. IT IS ALSO THE MINIMUM GAS PRICE This is arbitrary and needs to be reviewed. // It is also 0 in tests, so we need to increase to at least 1. - coin_price_per_unit: (MINUMUM_GAS_PRICE_IN_DIEM.max(min_gas_price.unwrap_or(1)) as f64 + coin_price_per_unit: (MINIMUM_GAS_PRICE_IN_DIEM.max(min_gas_price.unwrap_or(1)) as f64 * price_multiplier) as u64, // this is the minimum price //coin_price_per_unit: 100 as u64, @@ -612,6 +637,7 @@ impl Default for TxConfigs { } } +//////// TESTS //////// #[tokio::test] async fn test_create() { let a = AppCfg { diff --git a/types/src/core_types/dialogue.rs b/types/src/core_types/dialogue.depr similarity index 74% rename from types/src/core_types/dialogue.rs rename to types/src/core_types/dialogue.depr index 8bab02c05..83e0aa317 100644 --- a/types/src/core_types/dialogue.rs +++ b/types/src/core_types/dialogue.depr @@ -1,14 +1,11 @@ //! get home path or set it use anyhow::{bail, Error}; -use dialoguer::{Confirm, Input}; use diem_crypto::HashValue; use diem_global_constants::NODE_HOME; use diem_types::chain_id::MODE_0L; use glob::glob; use std::{fs, net::Ipv4Addr, path::PathBuf}; -use crate::block::VDFProof; - /// interact with user to get the home path for files pub fn what_home(swarm_path: Option, swarm_persona: Option) -> PathBuf { // For dev and CI setup @@ -148,42 +145,4 @@ fn swarm_home(mut swarm_path: PathBuf, swarm_persona: Option) -> PathBuf swarm_path } -// helper to parse the existing blocks in the miner's path. This function receives any path. Note: the path is configured in miner.toml which abscissa Configurable parses, see commands.rs. -fn _find_last_legacy_block(blocks_dir: &PathBuf) -> Result { - let mut max_block: Option = None; - let mut max_block_path = None; - // iterate through all json files in the directory. - for entry in glob(&format!("{}/block_*.json", blocks_dir.display())) - .expect("Failed to read glob pattern") - { - if let Ok(entry) = entry { - let block_file = - fs::read_to_string(&entry).expect("Could not read latest block file in path"); - - let block: VDFProof = serde_json::from_str(&block_file)?; - let blocknumber = block.height; - if max_block.is_none() { - max_block = Some(blocknumber); - max_block_path = Some(entry); - } else { - if blocknumber > max_block.unwrap() { - max_block = Some(blocknumber); - max_block_path = Some(entry); - } - } - } - } - - if let Some(p) = max_block_path { - let b = fs::read_to_string(p).expect("Could not read latest block file in path"); - match serde_json::from_str(&b) { - Ok(v) => Ok(v), - Err(e) => bail!(e), - } - } else { - bail!("cannot find a legacy block in: {:?}", blocks_dir) - } -} -fn _hash_last_proof(proof: &Vec) -> Vec { - HashValue::sha3_256_of(proof).to_vec() -} +// COMMIT NOTE: deprecated tower helpers diff --git a/types/src/core_types/mod.rs b/types/src/core_types/mod.rs index f3f2d8a81..643b7b7cb 100644 --- a/types/src/core_types/mod.rs +++ b/types/src/core_types/mod.rs @@ -4,3 +4,4 @@ pub mod fixtures; pub mod legacy_currency_info; pub mod mode_ol; pub mod network_playlist; +pub mod pledge; diff --git a/types/src/core_types/pledge.rs b/types/src/core_types/pledge.rs new file mode 100644 index 000000000..e86dd10c6 --- /dev/null +++ b/types/src/core_types/pledge.rs @@ -0,0 +1,120 @@ +//! User code of conduct pledges + +use diem::common::utils::prompt_yes; +use diem_crypto::HashValue; +use diem_types::chain_id::NamedChain; +use serde::{self, Deserialize, Serialize}; + +#[cfg(test)] +use crate::core_types::app_cfg::{AppCfg, Profile}; +use crate::core_types::mode_ol::MODE_0L; + +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct Pledge { + /// the canonical id of this pledge + pub id: u8, + /// nickname of pledge + pub name: String, + /// textual versions of the pledge + pub version: u8, + /// hash of the textual version + #[serde(with = "hex::serde")] + pub hash: Vec, + /// Text question + pub question: String, + /// Text preamble + pub preamble: String, + /// if this pledge been committed to chain + pub on_chain: bool, +} + +pub enum CanonicalPledges { + /// protect the game + Game = 0, + /// operate in good faith + Validator = 1, +} + +impl Pledge { + /// make the unique hex encoding of the text. + pub fn to_hash(&mut self) { + let mut concat = self.question.clone(); + concat.push_str(&self.preamble); + self.hash = HashValue::sha3_256_of(&concat.into_bytes()).to_vec(); + } + + /// check pledge hash + pub fn check_pledge_hash(pledge_idx: u8, bytes: &[u8]) -> bool { + if pledge_idx == 0 { + return bytes == Self::pledge_protect_the_game().hash; + } else if pledge_idx == 1 { + return bytes == Self::pledge_validator().hash; + } else { + assert!(pledge_idx < 2, "pledge index not found"); + } + false + } + + /// interact with user to get basic pledges, validator pledge optional on default setup + pub fn pledge_dialogue(&self) -> bool { + println!("PLEDGE #{}: {}\n\n{}", &self.id, &self.name, &self.preamble); + + if *MODE_0L != NamedChain::MAINNET { + println!("seems you are using CI or testnet settings, pledges default to yes"); + return true; + }; + if prompt_yes(&format!("\n{}", &self.question)) { + return true; + } + false + } + + /// #0 Protect the Game Pledge + /// Reference: Docs from Discord 0L Contributors circa June 2024 + pub fn pledge_protect_the_game() -> Pledge { + let mut p = Pledge { + id: 0, + name: "Protect the Game".to_string(), + version: 0, + question: "Do you pledge to not damage the game and never cheat other users?".to_string(), + preamble: "Code is not law at Open Libra. The law is law. The law comes from history.\n\nI understand written and unwritten laws come from social norms. I will refer to the expectations of this community based on canonical instructions, code documentation, and common sense to know when I'm cheating at the game, or otherwise unlawfully disadvantaging someone for my benefit.\n\nCheating can include, but is not limited to: gaining an advantage in a way that would be impossible unless it was covert, dishonest, untrue, or otherwise using an expected common courtesy others have extended to me which I'm not willing to return.".to_string(), + hash: vec![], + on_chain: false, + }; + + p.to_hash(); + + p + } + + /// #1 Validator pledge + /// Reference: Docs from Discord 0L Contributors circa June 2024 + pub fn pledge_validator() -> Pledge { + let mut p = Pledge { + id: 1, + name: "Operate in Good Faith".to_string(), + version: 0, + question: "Do you pledge to be a validator that acts in good faith to secure the network?".to_string(), + preamble: "When taking this pledge you are also taking the Protect the Game pledge:\n'I pledge to not damage the game and never cheat other users'.\n\nAdditionally you pledge to: obey the blockchain's policies as intended, some of which may be encoded as smart contracts, not pretend to be multiple people (sybil), not change the blockchain settings or software without consulting the community, run the blockchain security software (e.g validator, and fullnode software) as intended and in its entirety.".to_string(), + hash: vec![], + on_chain: false, + }; + + p.to_hash(); + + p + } +} + +#[tokio::test] +async fn test_pledge() { + let mut a = AppCfg { + user_profiles: vec![Profile::default()], + ..Default::default() + }; + let p = a.get_profile_mut(None).unwrap(); + assert!(p.pledges.is_none()); + let zero = Pledge::pledge_protect_the_game(); + p.pledges = Some(vec![zero]); + assert!(p.pledges.is_some()); +} diff --git a/types/src/ol_progress.rs b/types/src/ol_progress.rs index aeea455f5..a13b358d4 100644 --- a/types/src/ol_progress.rs +++ b/types/src/ol_progress.rs @@ -1,6 +1,6 @@ //! standardize cli progress bars in 0L tools use console::{self, style}; -use indicatif::{ProgressBar, ProgressStyle}; +use indicatif::{ProgressBar, ProgressIterator, ProgressStyle}; /// standard cli progress bars etc. for 0L tools pub struct OLProgress; @@ -30,9 +30,9 @@ impl OLProgress { pb } - /// For special occasions. Don't overuse it :) - pub fn fun() -> ProgressStyle { - ProgressStyle::with_template("Carpe Diem: {msg} {spinner}") + /// YAY, carpe diem + pub fn fun_style() -> ProgressStyle { + ProgressStyle::with_template(" CARPE DIEM\n{msg}\n{spinner}") .unwrap() // For more spinners check out the cli-spinners project: // https://github.com/sindresorhus/cli-spinners/blob/master/spinners.json @@ -47,6 +47,17 @@ impl OLProgress { ]) } + /// For special occasions. Don't overuse it :) + pub fn make_fun() { + let a = 0..10; + let wait = core::time::Duration::from_millis(500); + a.progress_with_style(Self::fun_style()) + // .with_message("message") + .for_each(|_| { + std::thread::sleep(wait); + }); + } + /// formatted "complete" message pub fn complete(msg: &str) { let prepad = format!("{} ", msg); @@ -111,9 +122,15 @@ fn progress() { std::thread::sleep(wait); }); - a.progress_with_style(OLProgress::fun()) + a.progress_with_style(OLProgress::fun_style()) .with_message("message") .for_each(|_| { std::thread::sleep(wait); }); } + +#[test] +#[ignore] +fn fun() { + OLProgress::make_fun(); +}