From 5edec4a6225413bf4c5763925b76f611de2da48e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 29 Nov 2022 16:39:04 +0100 Subject: [PATCH 01/10] refactor bech32m string encoding for address and MASP addresses --- core/src/types/address.rs | 95 ++++-------- core/src/types/key/common.rs | 1 + core/src/types/masp.rs | 242 ++++++++++++++---------------- core/src/types/mod.rs | 1 + core/src/types/string_encoding.rs | 121 +++++++++++++++ 5 files changed, 264 insertions(+), 196 deletions(-) create mode 100644 core/src/types/string_encoding.rs diff --git a/core/src/types/address.rs b/core/src/types/address.rs index 1a6611a2f5..c6be1ec222 100644 --- a/core/src/types/address.rs +++ b/core/src/types/address.rs @@ -6,30 +6,23 @@ use std::fmt::{Debug, Display}; use std::hash::Hash; use std::str::FromStr; -use bech32::{self, FromBase32, ToBase32, Variant}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use data_encoding::HEXUPPER; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; -use thiserror::Error; use crate::ibc::signer::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::Multitoken "; } +/// 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. @@ -100,24 +99,6 @@ const PREFIX_IBC: &str = "ibc"; /// Fixed-length address strings prefix for Ethereum addresses. const PREFIX_ETH: &str = "eth"; -#[allow(missing_docs)] -#[derive(Error, Debug)] -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")] - InvalidInnerEncoding(std::io::Error), -} - -/// 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, @@ -149,34 +130,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[..]) - .map_err(DecodeError::InvalidInnerEncoding) + string_encoding::Format::decode(string) } /// Try to get a raw hash of an address, only defined for established and @@ -196,7 +155,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 @@ -254,7 +213,7 @@ impl Address { } /// Try to parse an address from fixed-length utf-8 encoded address string. - fn try_from_fixed_len_string(buf: &mut &[u8]) -> std::io::Result { + pub fn try_from_fixed_len_string(buf: &mut &[u8]) -> std::io::Result { use std::io::{Error, ErrorKind}; let string = std::str::from_utf8(buf) .map_err(|err| Error::new(ErrorKind::InvalidData, err))?; @@ -378,6 +337,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]) -> std::result::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, @@ -402,26 +375,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; diff --git a/core/src/types/key/common.rs b/core/src/types/key/common.rs index b3c4f3a52f..01d2699119 100644 --- a/core/src/types/key/common.rs +++ b/core/src/types/key/common.rs @@ -18,6 +18,7 @@ use super::{ }; use crate::types::ethereum_events::EthAddress; use crate::types::key::{SignableBytes, StorageHasher}; +use crate::types::string_encoding; /// Public key #[derive( diff --git a/core/src/types/masp.rs b/core/src/types/masp.rs index 487dddf3e3..5c443140a9 100644 --- a/core/src/types/masp.rs +++ b/core/src/types/masp.rs @@ -8,17 +8,14 @@ use bech32::{FromBase32, ToBase32}; use borsh::{BorshDeserialize, BorshSerialize}; 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, @@ -34,51 +31,99 @@ 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) + } +} + +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(DecodeError::InvalidInnerEncoding) - .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 { @@ -155,78 +200,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( + Error::new( ErrorKind::InvalidData, "expected 43 bytes for the payment address", - )) + ) }; let addr_data_err = || { - DecodeError::InvalidInnerEncoding(Error::new( + Error::new( ErrorKind::InvalidData, "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) } } @@ -258,51 +270,25 @@ 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(DecodeError::InvalidInnerEncoding) .map(Self) } } +impl_display_and_from_str_via_format!(ExtendedSpendingKey); + impl From for masp_primitives::zip32::ExtendedSpendingKey { fn from(key: ExtendedSpendingKey) -> Self { key.0 diff --git a/core/src/types/mod.rs b/core/src/types/mod.rs index c35b314296..bb511163c7 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/string_encoding.rs b/core/src/types/string_encoding.rs new file mode 100644 index 0000000000..afa6594e2a --- /dev/null +++ b/core/src/types/string_encoding.rs @@ -0,0 +1,121 @@ +//! 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 bech32::{self, FromBase32, ToBase32, Variant}; +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"; + +#[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 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).map_err(DecodeError::InvalidBytes) + } + + /// 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() +} From 3e5393e225f23b87fcf6183835741859475b28d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 30 Nov 2022 15:58:00 +0100 Subject: [PATCH 02/10] add bech32m string encoding for `common::PublicKey` and `DkgPublicKey` --- apps/src/lib/config/genesis.rs | 24 +++---------------- core/src/types/key/common.rs | 26 ++++++++++----------- core/src/types/key/dkg_session_keys.rs | 32 ++++++++++---------------- core/src/types/key/mod.rs | 1 - core/src/types/string_encoding.rs | 4 ++++ 5 files changed, 32 insertions(+), 55 deletions(-) diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 5c86debcb6..729f5d092b 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -82,29 +82,11 @@ pub mod genesis_config { #[derive(Error, Debug)] pub enum HexKeyError { #[error("Invalid hex string: {0:?}")] - InvalidHexString(data_encoding::DecodeError), + InvalidHexString(#[from] data_encoding::DecodeError), #[error("Invalid sha256 checksum: {0}")] - InvalidSha256(TryFromSliceError), + InvalidSha256(#[from] 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) - } + InvalidPublicKey(#[from] common::DecodeError), } #[derive(Clone, Debug, Deserialize, Serialize)] diff --git a/core/src/types/key/common.rs b/core/src/types/key/common.rs index 01d2699119..637d5f8624 100644 --- a/core/src/types/key/common.rs +++ b/core/src/types/key/common.rs @@ -16,6 +16,7 @@ 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; @@ -71,24 +72,23 @@ 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.try_to_vec().unwrap())) - } -} +/// 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 { + BorshSerialize::try_to_vec(self).unwrap() + } + + fn decode_bytes(bytes: &[u8]) -> Result { + BorshDeserialize::try_from_slice(bytes) } } +impl_display_and_from_str_via_format!(PublicKey); + #[allow(missing_docs)] #[derive(Error, Debug)] pub enum EthAddressConvError { diff --git a/core/src/types/key/dkg_session_keys.rs b/core/src/types/key/dkg_session_keys.rs index f2cafb639c..15f299cf42 100644 --- a/core/src/types/key/dkg_session_keys.rs +++ b/core/src/types/key/dkg_session_keys.rs @@ -1,18 +1,16 @@ //! Utilities around the DKG session keys use std::cmp::Ordering; -use std::fmt::Display; use std::io::{Error, ErrorKind}; -use std::str::FromStr; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; -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 @@ -138,27 +136,21 @@ impl BorshSchema for DkgPublicKey { } } -impl Display for DkgPublicKey { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let vec = self - .try_to_vec() - .expect("Encoding public key shouldn't fail"); - 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.try_to_vec() + .expect("Encoding public key shouldn't fail") + } - 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) } } +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 8c59262f72..7c9709ef0a 100644 --- a/core/src/types/key/mod.rs +++ b/core/src/types/key/mod.rs @@ -188,7 +188,6 @@ pub trait PublicKey: + Display + Debug + PartialOrd - + FromStr + Hash + Send + Sync diff --git a/core/src/types/string_encoding.rs b/core/src/types/string_encoding.rs index afa6594e2a..2f12ccf373 100644 --- a/core/src/types/string_encoding.rs +++ b/core/src/types/string_encoding.rs @@ -28,6 +28,10 @@ pub const MASP_PAYMENT_ADDRESS_HRP: &str = "patest"; 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"; #[allow(missing_docs)] #[derive(Error, Debug)] From f24631ca2e1fd5319d0620d24bc03495c08a1bdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 7 Dec 2022 18:01:21 +0100 Subject: [PATCH 03/10] changelog: add #849 --- .../improvements/849-human-readable-string-encoding.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/849-human-readable-string-encoding.md diff --git a/.changelog/unreleased/improvements/849-human-readable-string-encoding.md b/.changelog/unreleased/improvements/849-human-readable-string-encoding.md new file mode 100644 index 0000000000..18efcca794 --- /dev/null +++ b/.changelog/unreleased/improvements/849-human-readable-string-encoding.md @@ -0,0 +1,2 @@ +- Added bech32m string encoding for `common::PublicKey` and `DkgPublicKey`. + ([#849](https://github.com/anoma/namada/pull/849)) From 2e592c53c2be612516256f7fb927d13ff6a4a11f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 8 Dec 2022 18:19:52 +0100 Subject: [PATCH 04/10] add `common::Signature` bech32m encoding --- core/src/types/key/common.rs | 14 ++++++++++++++ core/src/types/string_encoding.rs | 2 ++ 2 files changed, 16 insertions(+) diff --git a/core/src/types/key/common.rs b/core/src/types/key/common.rs index 637d5f8624..0991f7d581 100644 --- a/core/src/types/key/common.rs +++ b/core/src/types/key/common.rs @@ -245,6 +245,20 @@ 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 { + BorshSerialize::try_to_vec(self).unwrap() + } + + fn decode_bytes(bytes: &[u8]) -> Result { + BorshDeserialize::try_from_slice(bytes) + } +} + +impl_display_and_from_str_via_format!(Signature); + impl From for Signature { fn from(sig: ed25519::Signature) -> Self { Signature::Ed25519(sig) diff --git a/core/src/types/string_encoding.rs b/core/src/types/string_encoding.rs index 2f12ccf373..4dcfc8dca3 100644 --- a/core/src/types/string_encoding.rs +++ b/core/src/types/string_encoding.rs @@ -32,6 +32,8 @@ pub const MASP_EXT_SPENDING_KEY_HRP: &str = "xsktest"; 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)] From ea3882b38fb3b24d4c0a1d2d7c12ea8d10497f96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 8 Feb 2023 11:19:34 +0100 Subject: [PATCH 05/10] core/key: add a helper to gen ed25519 key in bech32m format --- core/src/types/key/common.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/core/src/types/key/common.rs b/core/src/types/key/common.rs index 0991f7d581..7f837cb5b3 100644 --- a/core/src/types/key/common.rs +++ b/core/src/types/key/common.rs @@ -383,3 +383,21 @@ impl super::SigScheme for SigScheme { } } } + +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); + } +} From 1868fba8b02a35f2bb201980d436dd8acb99e524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 8 Feb 2023 11:21:17 +0100 Subject: [PATCH 06/10] test/e2e/ledger_tests: use bech32m PK format for `init-account` cmd --- core/src/types/key/common.rs | 1 + tests/src/e2e/ledger_tests.rs | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/core/src/types/key/common.rs b/core/src/types/key/common.rs index 7f837cb5b3..07f0746c6e 100644 --- a/core/src/types/key/common.rs +++ b/core/src/types/key/common.rs @@ -384,6 +384,7 @@ impl super::SigScheme for SigScheme { } } +#[cfg(test)] mod tests { use super::*; use crate::types::key::ed25519; diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 8dc8face08..0054a13711 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -544,8 +544,8 @@ fn ledger_txs_and_queries() -> Result<()> { "--source", BERTHA, "--public-key", - // Value obtained from `namada::types::key::ed25519::tests::gen_keypair` - "001be519a321e29020fa3cbfbfd01bd5e92db134305609270b71dace25b5a21168", + // Value obtained from `cargo test gen_ed25519_keypair -- --nocapture` + "pktest1qzyhgvhrqaj5fut8n9j8wgtleysf4agfq5tnynecq3d63qqfy5a5y7e8exd", "--code-path", VP_USER_WASM, "--alias", @@ -559,8 +559,8 @@ fn ledger_txs_and_queries() -> Result<()> { "--node", &validator_one_rpc, ], - // 6. Submit a tx to withdraw from faucet account (requires PoW challenge - // solution) + // 6. Submit a tx to withdraw from faucet account (requires PoW + // challenge solution) vec![ "transfer", "--source", From 37c03156705c144bc5b0701975e5d9cf0b2a0194 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 31 Jul 2023 15:44:44 +0100 Subject: [PATCH 07/10] wasm: update checksums --- wasm/checksums.json | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index cb9ca8ed52..cfe6f80de9 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,21 +1,21 @@ { - "tx_bond.wasm": "tx_bond.08cedac1d825cd8ee9b04ef059bc68fee04f82fec57e0f6bc38123de8fb35b85.wasm", - "tx_bridge_pool.wasm": "tx_bridge_pool.c9ef2bdb19910e12eadabc7f0552b45c937bcf1f47ca09759e52112b68d94114.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.2be60082c6b891cb437b4392e1b2570f5d92fed35dd624cd079ea48c4bc7902c.wasm", - "tx_ibc.wasm": "tx_ibc.f8056f465d17043d2864309d1c50a897a028b6ebf9702e5c403e1bf8c670f6e6.wasm", - "tx_init_account.wasm": "tx_init_account.e91ab25cebd15af9a958eca4a34ba287804b1ff6ba4c82fc941f7f50816e9c4d.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.1683eb9a2b2616af21f4f55457f7e06e0ae0591f460ede412a548ea9b1ab694c.wasm", - "tx_init_validator.wasm": "tx_init_validator.67574f70c1f76c2c68498d17323793366c13d119480b6787c8c5ede312e51da0.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.e6375abba3f750700c06fbdeb2d5edec22b6a382116a67a7f9d7f7ba49f134e1.wasm", - "tx_transfer.wasm": "tx_transfer.e65b47bc94c5a09e3edc68298ad0e5e35041b91bc4d679bf5b7f433dffdce58e.wasm", - "tx_unbond.wasm": "tx_unbond.4fd70d297ccedb369bf88d8a8459a47ea733d329410387a6f80a0e026c6e480d.wasm", - "tx_unjail_validator.wasm": "tx_unjail_validator.b8e7204b14e15a3c395432f35788eca45ef4332916d96dfba87627a5717916de.wasm", - "tx_update_vp.wasm": "tx_update_vp.65c5ca3e48fdef70e696460eca7540cf6196511d76fb2465133b145409329b3e.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.e0a003d922230d32b741b57ca18913cbc4d5d2290f02cb37dfdaa7f27cebb486.wasm", - "tx_withdraw.wasm": "tx_withdraw.40499cb0e268d3cc3d9bca5dacca05d8df35f5b90adf4087a5171505472d921a.wasm", - "vp_implicit.wasm": "vp_implicit.57af3b7d2ee9e2c9d7ef1e33a85646abeea7ea02dad19b7e0b20b289de5a7ae9.wasm", - "vp_masp.wasm": "vp_masp.4d656f775b7462095e7d9ff533e346829fad02568b1cf92fa2f99cc0677720ce.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.b0c855f0356a468db1d26baebc988508d4824836b86adb94ac432368feb03ac4.wasm", - "vp_user.wasm": "vp_user.31967a7411caf61729e46840eec90d2db386cf4745f49d59086a43cc3c3e2d39.wasm", - "vp_validator.wasm": "vp_validator.0bca8e34f5d51c74b2861d588e3dd6a8e61de7c63d356af63e2182e030ac42ee.wasm" + "tx_bond.wasm": "tx_bond.32f61cc65f656ef97581cfcc0666b5bef6a22d9d81f6a9645fc57ba82a332da3.wasm", + "tx_bridge_pool.wasm": "tx_bridge_pool.34ecb19eed01f86d798f60fddaf30f943697c2a5254ba128d10ddea543b52e21.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.0d8d1b5959b4e96d7dd722f0e8aad0ab360ff25c55147cd9358cc225253926f9.wasm", + "tx_ibc.wasm": "tx_ibc.5bb5ae4ba9a4dbe59a205e4679660ed1b66c1cfeab6edb38c5fa41517c0bd477.wasm", + "tx_init_account.wasm": "tx_init_account.3e080586ab5fca06391b26bdcc3b7344bcdfed80cd29f99e9c76d6c5621e4a58.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.27fa918ad27d370b7bcb66c7e46d1577bcde4ddade2a8419915a08267f1316d4.wasm", + "tx_init_validator.wasm": "tx_init_validator.01ea9b8f1560461bb16f2f7e817c303b3f6a65ac877a7b2c5c2057761e6cc506.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.6e2b4bfcc999fa126f655fa4378a6c66263adca83c7dfc8532ee2dc495119e5d.wasm", + "tx_transfer.wasm": "tx_transfer.49aa26471105145718a6bd1a4432e0532175982eebbd3c1bdc3d17bd337e7e5c.wasm", + "tx_unbond.wasm": "tx_unbond.e45b0cfa03f9666571d107423f3555cf91f288a37dbfa2233711f280b7e968b8.wasm", + "tx_unjail_validator.wasm": "tx_unjail_validator.4bf3eae8e53162ea38f872c6af7eac3b01996751bc816537685f0a26fbc260bb.wasm", + "tx_update_vp.wasm": "tx_update_vp.a36b44a3f7c9f7f5c5f003e97846ca7780b02594393312978ecaa55c76d77cd8.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.7fc1fc9ea4ae346d7464ea3bc31c035b0b9b98d80d10a5c13658c69f0d5b8b95.wasm", + "tx_withdraw.wasm": "tx_withdraw.8025d7d3fe53773218ffe1fc88360f9ae8449a34925c7e86846082719a4cab60.wasm", + "vp_implicit.wasm": "vp_implicit.870a04dcbc5df1e21520053d5bfceea959d1e391f495d48c61c0d9422e433718.wasm", + "vp_masp.wasm": "vp_masp.15571daa7af62100e43c3182ecdb4e4f0d6556da105d2ffa36ba1c7bdcbbb76a.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.777ee18a41f5e77576c895854c548db0cce8ea10f56a9995edfd4932ad69b356.wasm", + "vp_user.wasm": "vp_user.df016a5d0054fed6facb4c25d051a7aad48dae501f295f2ff821c5308619635e.wasm", + "vp_validator.wasm": "vp_validator.ded69ec11639e5f41974a21acefbf3b95e444164dbfa52188eb3cea569e80595.wasm" } \ No newline at end of file From 76144638c461bbedb348387b6b94c7b6ab943182 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 2 Aug 2023 15:04:03 +0200 Subject: [PATCH 08/10] [feat]: Added pre-genesis command checks to client/wallet commands --- apps/src/bin/namada-node/cli.rs | 33 +- apps/src/lib/cli.rs | 291 +++++++++++++---- apps/src/lib/cli/client.rs | 64 ++-- apps/src/lib/cli/context.rs | 179 ++++++----- apps/src/lib/cli/wallet.rs | 139 ++++++--- apps/src/lib/client/tx.rs | 214 ++++++++----- apps/src/lib/config/global.rs | 17 +- apps/src/lib/wallet/cli_utils.rs | 515 ------------------------------- apps/src/lib/wallet/mod.rs | 1 - shared/src/ledger/args.rs | 38 +++ 10 files changed, 662 insertions(+), 829 deletions(-) delete mode 100644 apps/src/lib/wallet/cli_utils.rs 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/lib/cli.rs b/apps/src/lib/cli.rs index 1c0bdba287..1cb381c01d 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -675,7 +675,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"; @@ -683,12 +683,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::() } } @@ -866,7 +867,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"; @@ -874,11 +875,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::() } } @@ -2303,6 +2306,7 @@ pub mod args { use super::context::*; use super::utils::*; use super::{ArgGroup, ArgMatches}; + 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; @@ -2440,6 +2444,7 @@ pub mod args { "port-id", DefaultFn(|| PortId::from_str("transfer").unwrap()), ); + pub const PRE_GENESIS: ArgFlag = flag("pre-genesis"); pub const PROPOSAL_OFFLINE: ArgFlag = flag("offline"); pub const PROTOCOL_KEY: ArgOpt = arg_opt("protocol-key"); pub const PRE_GENESIS_PATH: ArgOpt = arg_opt("pre-genesis-path"); @@ -2695,15 +2700,17 @@ 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:: { - 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, gas_amount: self.gas_amount, - gas_payer: ctx.get(&self.gas_payer), - code_path: ctx.read_wasm(self.code_path), + gas_payer: chain_ctx.get(&self.gas_payer), + code_path: chain_ctx.read_wasm(self.code_path), } } } @@ -3109,7 +3116,7 @@ pub mod args { fn to_sdk(self, ctx: &mut Context) -> TxCustom { TxCustom:: { tx: self.tx.to_sdk(ctx), - code_path: ctx.read_wasm(self.code_path), + code_path: ctx.borrow_chain_or_exit().read_wasm(self.code_path), data_path: self.data_path.map(|data_path| { std::fs::read(data_path) .expect("Expected a file at given data path") @@ -3147,13 +3154,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(), } } @@ -3195,11 +3204,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, @@ -3260,12 +3271,14 @@ 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), - source: ctx.get(&self.source), + tx, + source: chain_ctx.get(&self.source), vp_code_path: self.vp_code_path.to_path_buf(), tx_code_path: self.tx_code_path.to_path_buf(), - public_key: ctx.get_cached(&self.public_key), + public_key: chain_ctx.get_cached(&self.public_key), } } } @@ -3307,15 +3320,23 @@ 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), - source: ctx.get(&self.source), + tx, + source: chain_ctx.get(&self.source), scheme: self.scheme, - account_key: self.account_key.map(|x| ctx.get_cached(&x)), - 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)), + account_key: self.account_key.map(|x| chain_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 @@ -3419,11 +3440,13 @@ pub mod args { impl CliToSdk> for TxUpdateVp { fn to_sdk(self, ctx: &mut Context) -> TxUpdateVp { + let tx = self.tx.to_sdk(ctx); + let chain_ctx = ctx.borrow_mut_chain_or_exit(); TxUpdateVp:: { - 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), } } } @@ -3458,12 +3481,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(), } } @@ -3507,11 +3532,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(), } } @@ -3576,7 +3603,7 @@ pub mod args { tx: self.tx.to_sdk(ctx), proposal_data: self.proposal_data, offline: self.offline, - native_token: ctx.native_token.clone(), + native_token: ctx.borrow_chain_or_exit().native_token.clone(), tx_code_path: self.tx_code_path, } } @@ -3727,9 +3754,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), } } } @@ -3860,10 +3889,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(), } } @@ -3898,7 +3929,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, } } @@ -3933,10 +3964,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, } } @@ -3978,10 +4011,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)), } } } @@ -4011,10 +4046,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)), } } } @@ -4050,7 +4087,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, } } @@ -4084,7 +4123,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, } } @@ -4122,7 +4161,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(), } @@ -4160,7 +4199,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(), } } @@ -4191,7 +4230,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, } } @@ -4225,7 +4264,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)), } } } @@ -4266,7 +4307,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), } } } @@ -4338,6 +4379,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, dump_tx: self.dump_tx, @@ -4514,11 +4556,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, } } @@ -4537,6 +4581,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.", @@ -4548,10 +4596,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, } } @@ -4565,6 +4615,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.", @@ -4574,11 +4628,34 @@ pub mod args { impl CliToSdk> for MaspPayAddrGen { fn to_sdk(self, ctx: &mut Context) -> MaspPayAddrGen { + use namada::ledger::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, } } } @@ -4589,11 +4666,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, } } @@ -4611,6 +4690,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.", + )) } } @@ -4665,12 +4748,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, } @@ -4689,6 +4774,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.", @@ -4710,12 +4799,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, } } @@ -4738,6 +4829,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() @@ -4750,9 +4845,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, } } @@ -4763,21 +4860,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() @@ -4786,18 +4893,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() @@ -4809,14 +4936,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.", + )) } } @@ -4824,7 +4958,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 { @@ -4838,6 +4977,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]) @@ -4846,15 +4989,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, } } @@ -4872,6 +5031,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.", + )) } } diff --git a/apps/src/lib/cli/client.rs b/apps/src/lib/cli/client.rs index 6b68316850..e064381831 100644 --- a/apps/src/lib/cli/client.rs +++ b/apps/src/lib/cli/client.rs @@ -41,8 +41,10 @@ impl CliApi { let dry_run = args.tx.dry_run; tx::submit_custom(&client, &mut ctx, args).await?; if !dry_run { - crate::wallet::save(&ctx.wallet) - .unwrap_or_else(|err| eprintln!("{}", err)); + crate::wallet::save( + &ctx.borrow_chain_or_exit().wallet, + ) + .unwrap_or_else(|err| eprintln!("{}", err)); } else { println!( "Transaction dry run. No addresses have been \ @@ -104,7 +106,8 @@ impl CliApi { tx::submit_init_account(&client, &mut ctx, args) .await?; if !dry_run { - crate::wallet::save(&ctx.wallet) + let chain_ctx = ctx.borrow_chain_or_exit(); + crate::wallet::save(&chain_ctx.wallet) .unwrap_or_else(|err| eprintln!("{}", err)); } else { println!( @@ -235,18 +238,19 @@ impl CliApi { .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); + let chain_ctx = ctx.borrow_mut_chain_or_exit(); let tx_args = args.tx.clone(); let (mut tx, addr, pk) = bridge_pool::build_bridge_pool_tx( &client, - &mut ctx.wallet, + &mut chain_ctx.wallet, args, ) .await .unwrap(); tx::submit_reveal_aux( &client, - &mut ctx, + chain_ctx, &tx_args, addr, pk.clone(), @@ -254,7 +258,7 @@ impl CliApi { ) .await?; signing::sign_tx( - &mut ctx.wallet, + &mut chain_ctx.wallet, &mut tx, &tx_args, &pk, @@ -262,7 +266,7 @@ impl CliApi { .await?; sdk_tx::process_tx( &client, - &mut ctx.wallet, + &mut chain_ctx.wallet, &tx_args, tx, ) @@ -303,9 +307,10 @@ impl CliApi { .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); + let chain_ctx = ctx.borrow_mut_chain_or_exit(); rpc::query_and_print_validator_state( &client, - &mut ctx.wallet, + &mut chain_ctx.wallet, args, ) .await; @@ -321,10 +326,11 @@ impl CliApi { .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); + let chain_ctx = ctx.borrow_mut_chain_or_exit(); rpc::query_transfers( &client, - &mut ctx.wallet, - &mut ctx.shielded, + &mut chain_ctx.wallet, + &mut chain_ctx.shielded, args, ) .await; @@ -340,8 +346,13 @@ impl CliApi { .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_conversions(&client, &mut ctx.wallet, args) - .await; + let chain_ctx = ctx.borrow_mut_chain_or_exit(); + rpc::query_conversions( + &client, + &mut chain_ctx.wallet, + args, + ) + .await; } Sub::QueryBlock(QueryBlock(mut args)) => { let client = client.unwrap_or_else(|| { @@ -364,10 +375,11 @@ impl CliApi { .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); + let chain_ctx = ctx.borrow_mut_chain_or_exit(); rpc::query_balance( &client, - &mut ctx.wallet, - &mut ctx.shielded, + &mut chain_ctx.wallet, + &mut chain_ctx.shielded, args, ) .await; @@ -383,7 +395,8 @@ impl CliApi { .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_bonds(&client, &mut ctx.wallet, args) + let chain_ctx = ctx.borrow_mut_chain_or_exit(); + rpc::query_bonds(&client, &mut chain_ctx.wallet, args) .await .expect("expected successful query of bonds"); } @@ -411,9 +424,10 @@ impl CliApi { .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); + let chain_ctx = ctx.borrow_mut_chain_or_exit(); rpc::query_and_print_commission_rate( &client, - &mut ctx.wallet, + &mut chain_ctx.wallet, args, ) .await; @@ -429,8 +443,13 @@ impl CliApi { .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_slashes(&client, &mut ctx.wallet, args) - .await; + let chain_ctx = ctx.borrow_mut_chain_or_exit(); + rpc::query_slashes( + &client, + &mut chain_ctx.wallet, + args, + ) + .await; } Sub::QueryDelegations(QueryDelegations(mut args)) => { let client = client.unwrap_or_else(|| { @@ -443,8 +462,13 @@ impl CliApi { .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - rpc::query_delegations(&client, &mut ctx.wallet, args) - .await; + let chain_ctx = ctx.borrow_mut_chain_or_exit(); + rpc::query_delegations( + &client, + &mut chain_ctx.wallet, + args, + ) + .await; } Sub::QueryFindValidator(QueryFindValidator(mut args)) => { let client = client.unwrap_or_else(|| { diff --git a/apps/src/lib/cli/context.rs b/apps/src/lib/cli/context.rs index ee3a4a8dfa..b529e9a7e6 100644 --- a/apps/src/lib/cli/context.rs +++ b/apps/src/lib/cli/context.rs @@ -14,6 +14,7 @@ use namada::types::key::*; use namada::types::masp::*; use super::args; +use crate::cli::utils; use crate::client::tx::CLIShieldedUtils; use crate::config::genesis::genesis_config; use crate::config::global::GlobalConfig; @@ -67,10 +68,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 @@ -82,51 +90,89 @@ 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())); - let genesis = genesis_config::read_genesis_config(&genesis_file_path); - let native_token = genesis.native_token; - let default_genesis = - genesis_config::open_genesis_config(genesis_file_path)?; - let wallet = crate::wallet::load_or_new_from_genesis( - &chain_dir, - default_genesis, - ); - - // 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; + + 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_file_path = global_args + .base_dir + .join(format!("{}.toml", default_chain_id.as_str())); + let genesis = + genesis_config::read_genesis_config(&genesis_file_path); + let native_token = genesis.native_token; + let default_genesis = + genesis_config::open_genesis_config(genesis_file_path)?; + let wallet = crate::wallet::load_or_new_from_genesis( + &chain_dir, + default_genesis, + ); + + // 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: CLIShieldedUtils::new(chain_dir), + native_token, + }) } - } + None => None, + }; + Ok(Self { global_args, - wallet, global_config, - config, - shielded: CLIShieldedUtils::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()) + } +} + +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 @@ -169,22 +215,9 @@ impl Context { /// Get the wasm directory configured for the chain. /// - /// Note that in "dev" build, this may be the root `wasm` dir. + /// TODO(Tomas): 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. @@ -228,7 +261,7 @@ pub fn read_or_try_new_global_config( /// Argument that can be given raw or found in the [`Context`]. #[derive(Debug, Clone)] pub struct FromContext { - raw: String, + pub raw: String, phantom: PhantomData, } @@ -286,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) } } @@ -296,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 { let raw = raw.as_ref(); @@ -339,7 +372,7 @@ impl ArgFromContext for Address { 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(); @@ -355,7 +388,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(); @@ -380,7 +413,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(); @@ -396,7 +429,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(); @@ -413,7 +446,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(); @@ -430,7 +463,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(); @@ -446,7 +479,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(); @@ -461,7 +494,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 7505c59efe..3395e1ac73 100644 --- a/apps/src/lib/cli/wallet.rs +++ b/apps/src/lib/cli/wallet.rs @@ -8,16 +8,17 @@ use color_eyre::eyre::Result; use itertools::sorted; use masp_primitives::zip32::ExtendedFullViewingKey; use namada::ledger::masp::find_valid_diversifier; -use namada::ledger::wallet::{DecryptionError, FindKeyError}; +use namada::ledger::wallet::{DecryptionError, FindKeyError, Wallet}; use namada::types::key::*; use namada::types::masp::{MaspValue, PaymentAddress}; 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::client::utils::PRE_GENESIS_DIR; use crate::wallet::{read_and_confirm_encryption_password, CliWalletUtils}; +use crate::{cli, wallet}; impl CliApi { pub fn handle_wallet_command( @@ -52,8 +53,8 @@ impl CliApi { cmds::WalletAddress::Find(cmds::AddressOrAliasFind(args)) => { address_or_alias_find(ctx, args) } - cmds::WalletAddress::List(cmds::AddressList) => { - address_list(ctx) + cmds::WalletAddress::List(cmds::AddressList(args)) => { + address_list(ctx, args) } cmds::WalletAddress::Add(cmds::AddressAdd(args)) => { address_add(ctx, args) @@ -70,9 +71,9 @@ impl CliApi { cmds::WalletMasp::AddAddrKey(cmds::MaspAddAddrKey(args)) => { address_key_add(ctx, args) } - cmds::WalletMasp::ListPayAddrs(cmds::MaspListPayAddrs) => { - payment_addresses_list(ctx) - } + cmds::WalletMasp::ListPayAddrs(cmds::MaspListPayAddrs( + args, + )) => payment_addresses_list(ctx, args), cmds::WalletMasp::ListKeys(cmds::MaspListKeys(args)) => { spending_keys_list(ctx, args) } @@ -91,9 +92,10 @@ fn address_key_find( args::AddrKeyFind { alias, unsafe_show_secret, + is_pre_genesis, }: args::AddrKeyFind, ) { - let mut wallet = ctx.wallet; + 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 @@ -125,10 +127,11 @@ fn spending_keys_list( ctx: Context, args::MaspKeysList { decrypt, + is_pre_genesis, unsafe_show_secret, }: args::MaspKeysList, ) { - let wallet = ctx.wallet; + 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() { @@ -192,8 +195,11 @@ fn spending_keys_list( } /// List payment addresses. -fn payment_addresses_list(ctx: Context) { - let wallet = ctx.wallet; +fn payment_addresses_list( + ctx: Context, + 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() { println!( @@ -216,10 +222,11 @@ fn spending_key_gen( args::MaspSpendKeyGen { alias, alias_force, + is_pre_genesis, unsafe_dont_encrypt, }: args::MaspSpendKeyGen, ) { - let mut wallet = ctx.wallet; + 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); @@ -232,12 +239,13 @@ fn spending_key_gen( /// Generate a shielded payment address from the given key. fn payment_address_gen( - ctx: Context, + mut ctx: Context, args::MaspPayAddrGen { alias, alias_force, viewing_key, pin, + .. }: args::MaspPayAddrGen, ) { let alias = alias.to_lowercase(); @@ -246,7 +254,7 @@ fn payment_address_gen( let payment_addr = viewing_key .to_payment_address(div) .expect("a PaymentAddress"); - let mut wallet = ctx.wallet; + let wallet = &mut ctx.borrow_mut_chain_or_exit().wallet; let alias = wallet .insert_payment_addr( alias, @@ -257,7 +265,7 @@ fn payment_address_gen( eprintln!("Payment address not added"); cli::safe_exit(1); }); - crate::wallet::save(&wallet).unwrap_or_else(|err| eprintln!("{}", err)); + crate::wallet::save(wallet).unwrap_or_else(|err| eprintln!("{}", err)); println!( "Successfully generated a payment address with the following alias: {}", alias, @@ -266,19 +274,20 @@ fn payment_address_gen( /// Add a viewing key, spending key, or payment address to wallet. fn address_key_add( - mut ctx: Context, + ctx: Context, 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 = ctx - .wallet + let alias = wallet .insert_viewing_key(alias, viewing_key, alias_force) .unwrap_or_else(|| { eprintln!("Viewing key not added"); @@ -289,8 +298,7 @@ fn address_key_add( MaspValue::ExtendedSpendingKey(spending_key) => { let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); - let alias = ctx - .wallet + let alias = wallet .encrypt_insert_spending_key( alias, spending_key, @@ -304,8 +312,7 @@ fn address_key_add( (alias, "spending key") } MaspValue::PaymentAddress(payment_addr) => { - let alias = ctx - .wallet + let alias = wallet .insert_payment_addr(alias, payment_addr, alias_force) .unwrap_or_else(|| { eprintln!("Payment address not added"); @@ -314,7 +321,7 @@ fn address_key_add( (alias, "payment address") } }; - crate::wallet::save(&ctx.wallet).unwrap_or_else(|err| eprintln!("{}", err)); + crate::wallet::save(&wallet).unwrap_or_else(|err| eprintln!("{}", err)); println!( "Successfully added a {} with the following alias to wallet: {}", typ, alias, @@ -333,7 +340,8 @@ fn key_and_address_restore( derivation_path, }: args::KeyAndAddressRestore, ) { - let mut wallet = ctx.wallet; + // TODO: Does this make sense as a pre-genesis command? + let mut wallet = load_wallet(ctx, false); let encryption_password = read_and_confirm_encryption_password(unsafe_dont_encrypt); let (alias, _key) = wallet @@ -367,11 +375,12 @@ fn key_and_address_gen( scheme, alias, alias_force, + is_pre_genesis, unsafe_dont_encrypt, derivation_path, }: args::KeyAndAddressGen, ) { - let mut wallet = ctx.wallet; + let mut wallet = load_wallet(ctx, is_pre_genesis); let encryption_password = read_and_confirm_encryption_password(unsafe_dont_encrypt); let mut rng = OsRng; @@ -407,10 +416,11 @@ fn key_find( public_key, alias, value, + is_pre_genesis, unsafe_show_secret, }: args::KeyFind, ) { - let mut wallet = ctx.wallet; + 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 => { @@ -447,10 +457,11 @@ fn key_list( ctx: Context, args::KeyList { decrypt, + is_pre_genesis, unsafe_show_secret, }: args::KeyList, ) { - let wallet = ctx.wallet; + let wallet = load_wallet(ctx, is_pre_genesis); let known_keys = wallet.get_keys(); if known_keys.is_empty() { println!( @@ -492,8 +503,14 @@ fn key_list( } /// Export a keypair to a file. -fn key_export(ctx: Context, args::KeyExport { alias }: args::KeyExport) { - let mut wallet = ctx.wallet; +fn key_export( + ctx: Context, + 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| { @@ -513,8 +530,11 @@ fn key_export(ctx: Context, args::KeyExport { alias }: args::KeyExport) { } /// List all known addresses. -fn address_list(ctx: Context) { - let wallet = ctx.wallet; +fn address_list( + ctx: Context, + 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() { println!( @@ -533,46 +553,56 @@ fn address_list(ctx: Context) { } /// Find address (alias) by its alias (address). -fn address_or_alias_find(ctx: Context, args: args::AddressOrAliasFind) { - let wallet = ctx.wallet; - if args.address.is_some() && args.alias.is_some() { +fn address_or_alias_find( + ctx: Context, + args::AddressOrAliasFind { + alias, + address, + is_pre_genesis, + }: args::AddressOrAliasFind, +) { + 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()) { 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() + 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()) { 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() + address.unwrap() ); } } } /// Add an address to the wallet. -fn address_add(ctx: Context, args: args::AddressAdd) { - let mut wallet = ctx.wallet; +fn address_add( + ctx: Context, + 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() { eprintln!("Address not added"); @@ -581,6 +611,17 @@ fn address_add(ctx: Context, args: args::AddressAdd) { crate::wallet::save(&wallet).unwrap_or_else(|err| eprintln!("{}", err)); println!( "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/tx.rs b/apps/src/lib/client/tx.rs index 974e940db4..0e974023b6 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -29,7 +29,7 @@ use namada::types::transaction::governance::{ProposalType, VoteProposalData}; use namada::types::transaction::{InitValidator, TxType}; use super::rpc; -use crate::cli::context::WalletAddress; +use crate::cli::context::{ChainContext, WalletAddress}; use crate::cli::{args, safe_exit, Context}; use crate::client::rpc::query_wasm_code_hash; use crate::client::signing::find_pk; @@ -42,7 +42,7 @@ use crate::wallet::{gen_validator_keys, read_and_confirm_encryption_password}; // Build a transaction to reveal the signer of the given transaction. pub async fn submit_reveal_aux( client: &C, - ctx: &mut Context, + ctx: &mut ChainContext, args: &args::Tx, addr: Option
, pk: common::PublicKey, @@ -80,11 +80,13 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { + let chain_ctx = ctx.borrow_mut_chain_or_exit(); let (mut tx, addr, pk) = - tx::build_custom(client, &mut ctx.wallet, args.clone()).await?; - submit_reveal_aux(client, ctx, &args.tx, addr, pk.clone(), &mut tx).await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + tx::build_custom(client, &mut chain_ctx.wallet, args.clone()).await?; + submit_reveal_aux(client, chain_ctx, &args.tx, addr, pk.clone(), &mut tx) + .await?; + signing::sign_tx(&mut chain_ctx.wallet, &mut tx, &args.tx, &pk).await?; + tx::process_tx(client, &mut chain_ctx.wallet, &args.tx, tx).await?; Ok(()) } @@ -97,11 +99,14 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { + let chain_ctx = ctx.borrow_mut_chain_or_exit(); let (mut tx, addr, pk) = - tx::build_update_vp(client, &mut ctx.wallet, args.clone()).await?; - submit_reveal_aux(client, ctx, &args.tx, addr, pk.clone(), &mut tx).await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + tx::build_update_vp(client, &mut chain_ctx.wallet, args.clone()) + .await?; + submit_reveal_aux(client, chain_ctx, &args.tx, addr, pk.clone(), &mut tx) + .await?; + signing::sign_tx(&mut chain_ctx.wallet, &mut tx, &args.tx, &pk).await?; + tx::process_tx(client, &mut chain_ctx.wallet, &args.tx, tx).await?; Ok(()) } @@ -114,11 +119,14 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { + let chain_ctx = ctx.borrow_mut_chain_or_exit(); let (mut tx, addr, pk) = - tx::build_init_account(client, &mut ctx.wallet, args.clone()).await?; - submit_reveal_aux(client, ctx, &args.tx, addr, pk.clone(), &mut tx).await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + tx::build_init_account(client, &mut chain_ctx.wallet, args.clone()) + .await?; + submit_reveal_aux(client, chain_ctx, &args.tx, addr, pk.clone(), &mut tx) + .await?; + signing::sign_tx(&mut chain_ctx.wallet, &mut tx, &args.tx, &pk).await?; + tx::process_tx(client, &mut chain_ctx.wallet, &args.tx, tx).await?; Ok(()) } @@ -145,11 +153,12 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { + let chain_ctx = ctx.borrow_mut_chain_or_exit(); let tx_args = args::Tx { chain_id: tx_args .clone() .chain_id - .or_else(|| Some(ctx.config.ledger.chain_id.clone())), + .or_else(|| Some(chain_ctx.config.ledger.chain_id.clone())), ..tx_args.clone() }; let alias = tx_args @@ -166,7 +175,8 @@ where println!("Generating validator account key..."); let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); - ctx.wallet + chain_ctx + .wallet .gen_key( scheme, Some(validator_key_alias.clone()), @@ -192,7 +202,8 @@ where println!("Generating consensus key..."); let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); - ctx.wallet + chain_ctx + .wallet .gen_key( // Note that TM only allows ed25519 for consensus key SchemeType::Ed25519, @@ -218,7 +229,8 @@ where println!("Generating Eth cold key..."); let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); - ctx.wallet + chain_ctx + .wallet .gen_key( // Note that ETH only allows secp256k1 SchemeType::Secp256k1, @@ -245,7 +257,8 @@ where println!("Generating Eth hot key..."); let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); - ctx.wallet + chain_ctx + .wallet .gen_key( // Note that ETH only allows secp256k1 SchemeType::Secp256k1, @@ -265,7 +278,7 @@ where } // Generate the validator keys let validator_keys = gen_validator_keys( - &mut ctx.wallet, + &mut chain_ctx.wallet, Some(eth_hot_pk.clone()), protocol_key, scheme, @@ -336,7 +349,7 @@ where let (mut tx, addr, pk) = tx::prepare_tx( client, - &mut ctx.wallet, + &mut chain_ctx.wallet, &tx_args, tx, TxSigningKey::WalletAddress(source), @@ -344,10 +357,10 @@ where false, ) .await?; - submit_reveal_aux(client, &mut ctx, &tx_args, addr, pk.clone(), &mut tx) + submit_reveal_aux(client, chain_ctx, &tx_args, addr, pk.clone(), &mut tx) .await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &tx_args, &pk).await?; - let result = tx::process_tx(client, &mut ctx.wallet, &tx_args, tx) + signing::sign_tx(&mut chain_ctx.wallet, &mut tx, &tx_args, &pk).await?; + let result = tx::process_tx(client, &mut chain_ctx.wallet, &tx_args, tx) .await? .initialized_accounts(); @@ -355,7 +368,9 @@ where let (validator_address_alias, validator_address) = match &result[..] { // There should be 1 account for the validator itself [validator_address] => { - if let Some(alias) = ctx.wallet.find_alias(validator_address) { + if let Some(alias) = + chain_ctx.wallet.find_alias(validator_address) + { (alias.clone(), validator_address.clone()) } else { eprintln!("Expected one account to be created"); @@ -368,22 +383,25 @@ where } }; // add validator address and keys to the wallet - ctx.wallet + chain_ctx + .wallet .add_validator_data(validator_address, validator_keys); - crate::wallet::save(&ctx.wallet) + crate::wallet::save(&chain_ctx.wallet) .unwrap_or_else(|err| eprintln!("{}", err)); - let tendermint_home = ctx.config.ledger.cometbft_dir(); + let tendermint_home = chain_ctx.config.ledger.cometbft_dir(); tendermint_node::write_validator_key(&tendermint_home, &consensus_key); tendermint_node::write_validator_state(tendermint_home); // Write Namada config stuff or figure out how to do the above // tendermint_node things two epochs in the future!!! - ctx.config.ledger.shell.tendermint_mode = TendermintMode::Validator; - ctx.config + chain_ctx.config.ledger.shell.tendermint_mode = + TendermintMode::Validator; + chain_ctx + .config .write( - &ctx.config.ledger.shell.base_dir, - &ctx.config.ledger.chain_id, + &chain_ctx.config.ledger.shell.base_dir, + &chain_ctx.config.ledger.chain_id, true, ) .unwrap(); @@ -528,23 +546,28 @@ pub async fn submit_transfer( mut ctx: Context, args: args::TxTransfer, ) -> Result<(), tx::Error> { + let chain_ctx = ctx.borrow_mut_chain_or_exit(); for _ in 0..2 { let arg = args.clone(); - let (mut tx, addr, pk, tx_epoch, _isf) = - tx::build_transfer(client, &mut ctx.wallet, &mut ctx.shielded, arg) - .await?; + let (mut tx, addr, pk, tx_epoch, _isf) = tx::build_transfer( + client, + &mut chain_ctx.wallet, + &mut chain_ctx.shielded, + arg, + ) + .await?; submit_reveal_aux( client, - &mut ctx, + chain_ctx, &args.tx, addr, pk.clone(), &mut tx, ) .await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; + signing::sign_tx(&mut chain_ctx.wallet, &mut tx, &args.tx, &pk).await?; let result = - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + tx::process_tx(client, &mut chain_ctx.wallet, &args.tx, tx).await?; // Query the epoch in which the transaction was probably submitted let submission_epoch = rpc::query_and_print_epoch(client).await; @@ -581,12 +604,14 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { + let chain_ctx = ctx.borrow_mut_chain_or_exit(); let (mut tx, addr, pk) = - tx::build_ibc_transfer(client, &mut ctx.wallet, args.clone()).await?; - submit_reveal_aux(client, &mut ctx, &args.tx, addr, pk.clone(), &mut tx) + tx::build_ibc_transfer(client, &mut chain_ctx.wallet, args.clone()) + .await?; + submit_reveal_aux(client, chain_ctx, &args.tx, addr, pk.clone(), &mut tx) .await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + signing::sign_tx(&mut chain_ctx.wallet, &mut tx, &args.tx, &pk).await?; + tx::process_tx(client, &mut chain_ctx.wallet, &args.tx, tx).await?; Ok(()) } @@ -599,6 +624,7 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { + let chain_ctx = ctx.borrow_mut_chain_or_exit(); let file = File::open(&args.proposal_data).expect("File must exist."); let proposal: Proposal = serde_json::from_reader(file).expect("JSON was not well-formatted"); @@ -662,10 +688,10 @@ where } if args.offline { - let signer = ctx.get(&signer); - let key = find_pk(client, &mut ctx.wallet, &signer).await?; + let signer = chain_ctx.get(&signer); + let key = find_pk(client, &mut chain_ctx.wallet, &signer).await?; let signing_key = - signing::find_key_by_pk(&mut ctx.wallet, &args.tx, &key)?; + signing::find_key_by_pk(&mut chain_ctx.wallet, &args.tx, &key)?; let offline_proposal = OfflineProposal::new(proposal, signer, &signing_key); let proposal_filename = args @@ -688,7 +714,7 @@ where } Ok(()) } else { - let signer = ctx.get(&signer); + let signer = chain_ctx.get(&signer); let tx_data = proposal.clone().try_into(); let (mut init_proposal_data, init_proposal_content, init_proposal_code) = if let Ok(data) = tx_data { @@ -698,9 +724,12 @@ where safe_exit(1) }; - let balance = - rpc::get_token_balance(client, &ctx.native_token, &proposal.author) - .await; + let balance = rpc::get_token_balance( + client, + &chain_ctx.native_token, + &proposal.author, + ) + .await; if balance < token::Amount::from_uint( governance_parameters.min_proposal_fund, @@ -726,7 +755,7 @@ where let tx_code_hash = query_wasm_code_hash(client, args::TX_INIT_PROPOSAL) .await .unwrap(); - tx.header.chain_id = ctx.config.ledger.chain_id.clone(); + tx.header.chain_id = chain_ctx.config.ledger.chain_id.clone(); tx.header.expiration = args.tx.expiration; // Put the content of this proposal into an extra section { @@ -752,7 +781,7 @@ where let (mut tx, addr, pk) = tx::prepare_tx( client, - &mut ctx.wallet, + &mut chain_ctx.wallet, &args.tx, tx, TxSigningKey::WalletAddress(signer), @@ -762,15 +791,15 @@ where .await?; submit_reveal_aux( client, - &mut ctx, + chain_ctx, &args.tx, addr, pk.clone(), &mut tx, ) .await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + signing::sign_tx(&mut chain_ctx.wallet, &mut tx, &args.tx, &pk).await?; + tx::process_tx(client, &mut chain_ctx.wallet, &args.tx, tx).await?; Ok(()) } } @@ -790,6 +819,7 @@ where eprintln!("Missing mandatory argument --signer."); safe_exit(1) }; + let chain_ctx = ctx.borrow_mut_chain_or_exit(); // Construct vote let proposal_vote = match args.vote.to_ascii_lowercase().as_str() { @@ -869,9 +899,9 @@ where safe_exit(1) } - let key = find_pk(client, &mut ctx.wallet, signer).await?; + let key = find_pk(client, &mut chain_ctx.wallet, signer).await?; let signing_key = - signing::find_key_by_pk(&mut ctx.wallet, &args.tx, &key)?; + signing::find_key_by_pk(&mut chain_ctx.wallet, &args.tx, &key)?; let offline_vote = OfflineVote::new( &proposal, proposal_vote, @@ -1021,7 +1051,7 @@ where let (mut tx, addr, pk) = tx::prepare_tx( client, - &mut ctx.wallet, + &mut chain_ctx.wallet, &args.tx, tx, TxSigningKey::WalletAddress(signer.clone()), @@ -1031,16 +1061,17 @@ where .await?; submit_reveal_aux( client, - &mut ctx, + chain_ctx, &args.tx, addr, pk.clone(), &mut tx, ) .await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk) + signing::sign_tx(&mut chain_ctx.wallet, &mut tx, &args.tx, &pk) + .await?; + tx::process_tx(client, &mut chain_ctx.wallet, &args.tx, tx) .await?; - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; Ok(()) } None => { @@ -1063,11 +1094,13 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { + let chain_ctx = ctx.borrow_mut_chain_or_exit(); let reveal_tx = - tx::build_reveal_pk(client, &mut ctx.wallet, args.clone()).await?; + tx::build_reveal_pk(client, &mut chain_ctx.wallet, args.clone()) + .await?; if let Some((mut tx, _, pk)) = reveal_tx { - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + signing::sign_tx(&mut chain_ctx.wallet, &mut tx, &args.tx, &pk).await?; + tx::process_tx(client, &mut chain_ctx.wallet, &args.tx, tx).await?; } Ok(()) } @@ -1139,11 +1172,14 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { + let chain_ctx = ctx.borrow_mut_chain_or_exit(); let (mut tx, addr, pk) = - tx::build_bond::(client, &mut ctx.wallet, args.clone()).await?; - submit_reveal_aux(client, ctx, &args.tx, addr, pk.clone(), &mut tx).await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + tx::build_bond::(client, &mut chain_ctx.wallet, args.clone()) + .await?; + submit_reveal_aux(client, chain_ctx, &args.tx, addr, pk.clone(), &mut tx) + .await?; + signing::sign_tx(&mut chain_ctx.wallet, &mut tx, &args.tx, &pk).await?; + tx::process_tx(client, &mut chain_ctx.wallet, &args.tx, tx).await?; Ok(()) } @@ -1156,11 +1192,13 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { + let chain_ctx = ctx.borrow_mut_chain_or_exit(); let (mut tx, addr, pk, latest_withdrawal_pre) = - tx::build_unbond(client, &mut ctx.wallet, args.clone()).await?; - submit_reveal_aux(client, ctx, &args.tx, addr, pk.clone(), &mut tx).await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + tx::build_unbond(client, &mut chain_ctx.wallet, args.clone()).await?; + submit_reveal_aux(client, chain_ctx, &args.tx, addr, pk.clone(), &mut tx) + .await?; + signing::sign_tx(&mut chain_ctx.wallet, &mut tx, &args.tx, &pk).await?; + tx::process_tx(client, &mut chain_ctx.wallet, &args.tx, tx).await?; tx::query_unbonds(client, args.clone(), latest_withdrawal_pre).await?; Ok(()) } @@ -1174,12 +1212,13 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { + let chain_ctx = ctx.borrow_mut_chain_or_exit(); let (mut tx, addr, pk) = - tx::build_withdraw(client, &mut ctx.wallet, args.clone()).await?; - submit_reveal_aux(client, &mut ctx, &args.tx, addr, pk.clone(), &mut tx) + tx::build_withdraw(client, &mut chain_ctx.wallet, args.clone()).await?; + submit_reveal_aux(client, chain_ctx, &args.tx, addr, pk.clone(), &mut tx) .await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + signing::sign_tx(&mut chain_ctx.wallet, &mut tx, &args.tx, &pk).await?; + tx::process_tx(client, &mut chain_ctx.wallet, &args.tx, tx).await?; Ok(()) } @@ -1192,14 +1231,18 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { + let chain_ctx = ctx.borrow_mut_chain_or_exit(); let arg = args.clone(); - let (mut tx, addr, pk) = - tx::build_validator_commission_change(client, &mut ctx.wallet, arg) - .await?; - submit_reveal_aux(client, &mut ctx, &args.tx, addr, pk.clone(), &mut tx) + let (mut tx, addr, pk) = tx::build_validator_commission_change( + client, + &mut chain_ctx.wallet, + arg, + ) + .await?; + submit_reveal_aux(client, chain_ctx, &args.tx, addr, pk.clone(), &mut tx) .await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + signing::sign_tx(&mut chain_ctx.wallet, &mut tx, &args.tx, &pk).await?; + tx::process_tx(client, &mut chain_ctx.wallet, &args.tx, tx).await?; Ok(()) } @@ -1214,13 +1257,14 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { + let chain_ctx = ctx.borrow_mut_chain_or_exit(); let (mut tx, addr, pk) = - tx::build_unjail_validator(client, &mut ctx.wallet, args.clone()) + tx::build_unjail_validator(client, &mut chain_ctx.wallet, args.clone()) .await?; - submit_reveal_aux(client, &mut ctx, &args.tx, addr, pk.clone(), &mut tx) + submit_reveal_aux(client, chain_ctx, &args.tx, addr, pk.clone(), &mut tx) .await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + signing::sign_tx(&mut chain_ctx.wallet, &mut tx, &args.tx, &pk).await?; + tx::process_tx(client, &mut chain_ctx.wallet, &args.tx, tx).await?; Ok(()) } diff --git a/apps/src/lib/config/global.rs b/apps/src/lib/config/global.rs index f1d8e5d483..b036f6b694 100644 --- a/apps/src/lib/config/global.rs +++ b/apps/src/lib/config/global.rs @@ -29,26 +29,27 @@ pub type Result = std::result::Result; #[derive(Debug, 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. 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/wallet/cli_utils.rs b/apps/src/lib/wallet/cli_utils.rs deleted file mode 100644 index ffaf080841..0000000000 --- a/apps/src/lib/wallet/cli_utils.rs +++ /dev/null @@ -1,515 +0,0 @@ -use std::fs::File; -use std::io::{self, Write}; - -use borsh::BorshSerialize; -use itertools::sorted; -use masp_primitives::zip32::ExtendedFullViewingKey; -use namada::ledger::masp::find_valid_diversifier; -use namada::ledger::wallet::{DecryptionError, FindKeyError}; -use namada::types::key::{PublicKeyHash, RefTo}; -use namada::types::masp::{MaspValue, PaymentAddress}; -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, - 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) = wallet - .gen_key( - scheme, - alias, - alias_force, - encryption_password, - derivation_path_and_mnemonic_rng, - ) - .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 - ); -} - -/// 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 - .try_to_vec() - .expect("Encoding keypair shouldn't fail"); - 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/mod.rs b/apps/src/lib/wallet/mod.rs index 7c9c193087..73f48fbb2d 100644 --- a/apps/src/lib/wallet/mod.rs +++ b/apps/src/lib/wallet/mod.rs @@ -1,4 +1,3 @@ -pub mod cli_utils; pub mod defaults; pub mod pre_genesis; mod store; diff --git a/shared/src/ledger/args.rs b/shared/src/ledger/args.rs index 029c77ff0e..bf15885ca7 100644 --- a/shared/src/ledger/args.rs +++ b/shared/src/ledger/args.rs @@ -480,6 +480,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, } @@ -491,6 +494,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, } @@ -506,6 +511,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 @@ -517,6 +524,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 @@ -547,6 +556,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, } @@ -558,6 +569,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 @@ -565,15 +578,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, } @@ -583,6 +608,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 @@ -592,6 +619,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 @@ -603,6 +639,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. From 65a07be9ecb1a14a67f969376e1ff8e394eb18a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 3 Aug 2023 09:27:52 +0100 Subject: [PATCH 09/10] cli/client: refactor saving of wallet of custom and transfer tx --- apps/src/lib/cli/client.rs | 23 ----------------------- apps/src/lib/client/tx.rs | 12 ++++++++++++ 2 files changed, 12 insertions(+), 23 deletions(-) diff --git a/apps/src/lib/cli/client.rs b/apps/src/lib/cli/client.rs index e064381831..7266efbe68 100644 --- a/apps/src/lib/cli/client.rs +++ b/apps/src/lib/cli/client.rs @@ -38,19 +38,7 @@ impl CliApi { .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - let dry_run = args.tx.dry_run; tx::submit_custom(&client, &mut ctx, args).await?; - if !dry_run { - crate::wallet::save( - &ctx.borrow_chain_or_exit().wallet, - ) - .unwrap_or_else(|err| eprintln!("{}", err)); - } else { - println!( - "Transaction dry run. No addresses have been \ - saved." - ) - } } Sub::TxTransfer(TxTransfer(mut args)) => { let client = client.unwrap_or_else(|| { @@ -102,19 +90,8 @@ impl CliApi { .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - let dry_run = args.tx.dry_run; tx::submit_init_account(&client, &mut ctx, args) .await?; - if !dry_run { - let chain_ctx = ctx.borrow_chain_or_exit(); - crate::wallet::save(&chain_ctx.wallet) - .unwrap_or_else(|err| eprintln!("{}", err)); - } else { - println!( - "Transaction dry run. No addresses have been \ - saved." - ) - } } Sub::TxInitValidator(TxInitValidator(mut args)) => { let client = client.unwrap_or_else(|| { diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 0e974023b6..29a34e812f 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -87,6 +87,12 @@ where .await?; signing::sign_tx(&mut chain_ctx.wallet, &mut tx, &args.tx, &pk).await?; tx::process_tx(client, &mut chain_ctx.wallet, &args.tx, tx).await?; + if !args.tx.dry_run { + crate::wallet::save(&chain_ctx.wallet) + .unwrap_or_else(|err| eprintln!("{}", err)); + } else { + println!("Transaction dry run. No addresses have been saved.") + } Ok(()) } @@ -127,6 +133,12 @@ where .await?; signing::sign_tx(&mut chain_ctx.wallet, &mut tx, &args.tx, &pk).await?; tx::process_tx(client, &mut chain_ctx.wallet, &args.tx, tx).await?; + if !args.tx.dry_run { + crate::wallet::save(&chain_ctx.wallet) + .unwrap_or_else(|err| eprintln!("{}", err)); + } else { + println!("Transaction dry run. No addresses have been saved.") + } Ok(()) } From ae8262659f7b954554119b16431a79063592ee96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 7 Dec 2022 18:09:40 +0100 Subject: [PATCH 10/10] changelog: add #1782 --- .changelog/unreleased/features/1782-pre-genesis-wallet.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changelog/unreleased/features/1782-pre-genesis-wallet.md diff --git a/.changelog/unreleased/features/1782-pre-genesis-wallet.md b/.changelog/unreleased/features/1782-pre-genesis-wallet.md new file mode 100644 index 0000000000..f5cf71aaa7 --- /dev/null +++ b/.changelog/unreleased/features/1782-pre-genesis-wallet.md @@ -0,0 +1,7 @@ +- 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`. + ([#1782](https://github.com/anoma/namada/pull/1782))