feat: move shared cli args to config file (#157)

This commit is contained in:
Daniel Gushchyan 2024-08-14 23:20:31 -07:00 committed by GitHub
parent f93da16c47
commit 72c7702719
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 576 additions and 236 deletions

1
.gitignore vendored
View file

@ -12,3 +12,4 @@ artifacts/
.vscode/
.DS_Store
**/.env.local
cli/quartz.toml

88
Cargo.lock generated
View file

@ -382,6 +382,15 @@ dependencies = [
"tungstenite",
]
[[package]]
name = "atomic"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994"
dependencies = [
"bytemuck",
]
[[package]]
name = "atomic-waker"
version = "1.1.2"
@ -650,6 +659,12 @@ version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]]
name = "bytemuck"
version = "1.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "102087e286b4677862ea56cf8fc58bb2cdfa8725c40ffb80fe3a008eb7f2fc83"
[[package]]
name = "byteorder"
version = "1.5.0"
@ -1690,6 +1705,20 @@ version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
[[package]]
name = "figment"
version = "0.10.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3"
dependencies = [
"atomic",
"pear",
"serde",
"toml",
"uncased",
"version_check",
]
[[package]]
name = "fixedbitset"
version = "0.4.2"
@ -2536,6 +2565,12 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "inlinable_string"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb"
[[package]]
name = "inout"
version = "0.1.3"
@ -3309,6 +3344,29 @@ dependencies = [
"hmac",
]
[[package]]
name = "pear"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdeeaa00ce488657faba8ebf44ab9361f9365a97bd39ffb8a60663f57ff4b467"
dependencies = [
"inlinable_string",
"pear_codegen",
"yansi",
]
[[package]]
name = "pear_codegen"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bab5b985dc082b345f812b7df84e1bef27e7207b39e448439ba8bd69c93f147"
dependencies = [
"proc-macro2",
"proc-macro2-diagnostics",
"quote",
"syn 2.0.72",
]
[[package]]
name = "peg"
version = "0.8.4"
@ -3525,6 +3583,19 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "proc-macro2-diagnostics"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
"version_check",
"yansi",
]
[[package]]
name = "prodash"
version = "28.0.0"
@ -3631,6 +3702,7 @@ dependencies = [
"cycles-sync",
"dirs",
"displaydoc",
"figment",
"futures-util",
"hex",
"k256",
@ -3651,6 +3723,7 @@ dependencies = [
"thiserror",
"tm-prover",
"tokio",
"toml",
"tonic 0.12.1",
"tracing",
"tracing-subscriber",
@ -5498,6 +5571,15 @@ version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9"
[[package]]
name = "uncased"
version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697"
dependencies = [
"version_check",
]
[[package]]
name = "unicode-bidi"
version = "0.3.15"
@ -5950,6 +6032,12 @@ dependencies = [
"time",
]
[[package]]
name = "yansi"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
[[package]]
name = "zerocopy"
version = "0.7.35"

View file

@ -1,5 +1,4 @@
[template]
name = "quartz-app-template"
description = "An functioning example of a quartz app"
ignore = [
@ -9,5 +8,7 @@ ignore = [
"enclave/target",
"enclave/target/*",
"frontend/package-lock.json",
"frontend/public/images"
"frontend/public/images",
"trusted.hash",
"trusted.height"
]

View file

@ -0,0 +1,8 @@
mock_sgx = false
tx_sender = "admin"
chain_id = "testing"
node_url = "http://127.0.0.1:26657"
enclave_rpc_addr = "http://127.0.0.1"
enclave_rpc_port = 11090
trusted_hash = ""
trusted_height = 0

View file

@ -33,6 +33,8 @@ base64 = "0.22.1"
subtle-encoding = "0.5.1"
futures-util = "0.3.30"
target-lexicon = "0.12.16"
regex = "1.10.5"
toml = "0.8.19"
# cosmos
cosmrs.workspace = true
@ -46,4 +48,4 @@ tm-prover = { workspace = true}
quartz-common = { workspace = true, features=["contract"]}
quartz-tee-ra = { workspace = true}
mtcs-enclave = { workspace = true, optional = false}
regex = "1.10.5"
figment = { version = "0.10.19", features=["env", "toml"] }

View file

@ -1,12 +1,14 @@
use std::{env, path::PathBuf};
use std::path::PathBuf;
use clap::{Parser, Subcommand};
use cosmrs::{tendermint::chain::Id as ChainId, AccountId};
use figment::{providers::Serialized, Figment};
use serde::{Deserialize, Serialize};
use tracing::metadata::LevelFilter;
use crate::handler::utils::helpers::wasmaddr_to_id;
#[derive(clap::Args, Debug, Clone)]
#[derive(clap::Args, Debug, Clone, Serialize)]
pub struct Verbosity {
/// Increase verbosity, can be repeated up to 2 times
#[arg(long, short, action = clap::ArgAction::Count)]
@ -23,61 +25,45 @@ impl Verbosity {
}
}
#[derive(Debug, Parser)]
#[derive(Debug, Parser, Serialize)]
#[command(version, long_about = None)]
pub struct Cli {
/// Increase log verbosity
#[clap(flatten)]
#[command(flatten)]
pub verbose: Verbosity,
/// Enable mock SGX mode for testing purposes.
/// This flag disables the use of an Intel SGX processor and allows the system to run without remote attestations.
#[clap(long, env)]
pub mock_sgx: bool,
#[arg(long)]
#[serde(skip_serializing_if = "Option::is_none")]
pub mock_sgx: Option<bool>,
/// Path to Quartz app directory
/// Defaults to current working dir
/// For quartz init, root serves as the parent directory of the directory in which the quartz app is generated
#[arg(long)]
#[serde(skip_serializing_if = "Option::is_none")]
pub app_dir: Option<PathBuf>,
/// Main command
#[command(subcommand)]
pub command: Command,
}
#[derive(Debug, Subcommand)]
#[derive(Debug, Subcommand, Serialize, Clone)]
pub enum Command {
/// Create an empty Quartz app from a template
Init {
/// the name of your Quartz app directory, defaults to quartz_app
#[clap(long, default_value = "quartz_app")]
name: String,
},
Init(InitArgs),
/// Perform handshake
Handshake {
/// path to create & init a Quartz app, defaults to current path if unspecified
#[arg(short, long, value_parser = wasmaddr_to_id)]
contract: AccountId,
/// Port enclave is listening on
#[arg(short, long, default_value = "11090")]
port: u16,
/// Name or address of private key with which to sign
#[arg(short, long, default_value = "admin")]
sender: String,
/// The network chain ID
#[arg(long, default_value = "testing")]
chain_id: ChainId,
/// <host>:<port> to tendermint rpc interface for this chain
#[clap(long, default_value_t = default_node_url())]
node_url: String,
/// RPC interface for the Quartz enclave
#[clap(long, default_value_t = default_rpc_addr())]
enclave_rpc_addr: String,
/// Path to Quartz app directory
/// Defaults to current working dir
#[clap(long)]
app_dir: Option<PathBuf>,
},
Handshake(HandshakeArgs),
/// Subcommands for handling the Quartz app contract
Contract {
#[command(subcommand)]
contract_command: ContractCommand,
},
/// Subcommands for handling the Quartz app enclave
Enclave {
#[command(subcommand)]
@ -85,58 +71,131 @@ pub enum Command {
},
}
#[derive(Debug, Clone, Subcommand)]
#[derive(Debug, Clone, Subcommand, Serialize)]
pub enum ContractCommand {
Build {
#[clap(long)]
manifest_path: PathBuf,
},
Deploy {
/// Json-formatted cosmwasm contract initialization message
#[clap(long, default_value = "{}")]
init_msg: String,
/// <host>:<port> to tendermint rpc interface for this chain
#[clap(long, default_value_t = default_node_url())]
node_url: String,
/// Name or address of private key with which to sign
#[arg(short, long, default_value = "admin")]
sender: String,
/// The network chain ID
#[arg(long, default_value = "testing")]
chain_id: ChainId,
/// A human-readable name for this contract in lists
#[arg(long, default_value = "Quartz App Contract")]
label: String,
/// Path to contract wasm binary for deployment
#[clap(long)]
wasm_bin_path: PathBuf,
},
Build(ContractBuildArgs),
Deploy(ContractDeployArgs),
}
#[derive(Debug, Clone, Subcommand)]
#[derive(Debug, Clone, Subcommand, Serialize)]
pub enum EnclaveCommand {
/// Build the Quartz app's enclave
Build {
/// Path to Cargo.toml file of the Quartz app's enclave package, defaults to './enclave/Cargo.toml' if unspecified
#[arg(long, default_value = "./enclave/Cargo.toml")]
manifest_path: PathBuf,
},
// Run the Quartz app's enclave
Start {
/// Path to quartz app directory
/// Defaults to current working dir
#[clap(long)]
app_dir: Option<PathBuf>,
/// The network chain ID
#[clap(long)]
chain_id: String,
},
Build(EnclaveBuildArgs),
/// Run the Quartz app's enclave
Start(EnclaveStartArgs),
}
fn default_rpc_addr() -> String {
env::var("RPC_URL").unwrap_or_else(|_| "http://127.0.0.1".to_string())
#[derive(Debug, Parser, Clone, Serialize, Deserialize)]
pub struct InitArgs {
/// The name of your Quartz app directory, defaults to quartz_app
#[arg(default_value = "quartz_app")]
pub name: PathBuf,
}
fn default_node_url() -> String {
env::var("NODE_URL").unwrap_or_else(|_| "http://127.0.0.1:26657".to_string())
#[derive(Debug, Parser, Clone, Serialize, Deserialize)]
pub struct HandshakeArgs {
/// Path to create & init a Quartz app, defaults to current path if unspecified
#[arg(short, long, value_parser = wasmaddr_to_id)]
pub contract: AccountId,
/// Name or address of private key with which to sign
#[arg(long)]
#[serde(skip_serializing_if = "Option::is_none")]
pub tx_sender: Option<String>,
/// The network chain ID
#[arg(long)]
#[serde(skip_serializing_if = "Option::is_none")]
pub chain_id: Option<ChainId>,
/// <host>:<port> to tendermint rpc interface for this chain
#[arg(long)]
#[serde(skip_serializing_if = "Option::is_none")]
pub node_url: Option<String>,
/// RPC interface for the Quartz enclave
#[arg(long)]
#[serde(skip_serializing_if = "Option::is_none")]
pub enclave_rpc_addr: Option<String>,
/// Port enclave is listening on
#[arg(long)]
#[serde(skip_serializing_if = "::std::option::Option::is_none")]
pub enclave_rpc_port: Option<u16>,
}
#[derive(Debug, Parser, Clone, Serialize, Deserialize)]
pub struct ContractBuildArgs {
#[arg(long)]
pub manifest_path: PathBuf,
}
#[derive(Debug, Parser, Clone, Serialize, Deserialize)]
pub struct ContractDeployArgs {
/// Json-formatted cosmwasm contract initialization message
#[arg(long, default_value = "{}")]
pub init_msg: String,
/// <host>:<port> to tendermint rpc interface for this chain
#[arg(long)]
#[serde(skip_serializing_if = "Option::is_none")]
pub node_url: Option<String>,
/// Name or address of private key with which to sign
#[arg(long)]
#[serde(skip_serializing_if = "Option::is_none")]
pub tx_sender: Option<String>,
/// The network chain ID
#[arg(long)]
#[serde(skip_serializing_if = "Option::is_none")]
pub chain_id: Option<ChainId>,
/// A human-readable name for this contract in lists
#[arg(long, default_value = "Quartz App Contract")]
pub label: String,
/// Path to contract wasm binary for deployment
#[arg(long)]
pub wasm_bin_path: PathBuf,
}
#[derive(Debug, Parser, Clone, Serialize, Deserialize)]
pub struct EnclaveBuildArgs {
/// Path to Cargo.toml file of the Quartz app's enclave package, defaults to './enclave/Cargo.toml' if unspecified
#[arg(long, default_value = "./enclave/Cargo.toml")]
pub manifest_path: PathBuf,
}
#[derive(Debug, Parser, Clone, Serialize, Deserialize)]
pub struct EnclaveStartArgs {
/// The network chain ID
#[arg(long)]
#[serde(skip_serializing_if = "Option::is_none")]
pub chain_id: Option<ChainId>,
/// Fetch latest trusted hash and height from the chain instead of existing configuration
#[arg(long)]
pub use_latest_trusted: bool,
}
pub trait ToFigment {
fn to_figment(&self) -> Figment;
}
impl ToFigment for Command {
fn to_figment(&self) -> Figment {
match self {
Command::Init(args) => Figment::from(Serialized::defaults(args)),
Command::Handshake(args) => Figment::from(Serialized::defaults(args)),
Command::Contract { contract_command } => match contract_command {
ContractCommand::Build(args) => Figment::from(Serialized::defaults(args)),
ContractCommand::Deploy(args) => Figment::from(Serialized::defaults(args)),
},
Command::Enclave { enclave_command } => match enclave_command {
EnclaveCommand::Build(args) => Figment::from(Serialized::defaults(args)),
EnclaveCommand::Start(args) => Figment::from(Serialized::defaults(args)),
},
}
}
}

85
cli/src/config.rs Normal file
View file

@ -0,0 +1,85 @@
use std::{env, path::PathBuf};
use cosmrs::tendermint::chain::Id as ChainId;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Config {
/// Enable mock SGX mode for testing purposes.
/// This flag disables the use of an Intel SGX processor and allows the system to run without remote attestations.
#[serde(default)]
pub mock_sgx: bool,
/// Name or address of private key with which to sign
#[serde(default = "default_tx_sender")]
pub tx_sender: String,
/// The network chain ID
#[serde(default = "default_chain_id")]
pub chain_id: ChainId,
/// <host>:<port> to tendermint rpc interface for this chain
#[serde(default = "default_node_url")]
pub node_url: String,
/// RPC interface for the Quartz enclave
#[serde(default = "default_rpc_addr")]
pub enclave_rpc_addr: String,
/// Port enclave is listening on
#[serde(default = "default_port")]
pub enclave_rpc_port: u16,
/// Path to Quartz app directory
/// Defaults to current working dir
#[serde(default = "default_app_dir")]
pub app_dir: PathBuf,
/// Trusted height for light client proofs
#[serde(default)]
pub trusted_height: u64,
/// Trusted hash for block at trusted_height for light client proofs
#[serde(default)]
pub trusted_hash: String,
}
fn default_rpc_addr() -> String {
env::var("RPC_URL").unwrap_or_else(|_| "http://127.0.0.1".to_string())
}
fn default_node_url() -> String {
env::var("NODE_URL").unwrap_or_else(|_| "http://127.0.0.1:26657".to_string())
}
fn default_tx_sender() -> String {
String::from("admin")
}
fn default_chain_id() -> ChainId {
"testing".parse().expect("default chain_id failed")
}
fn default_port() -> u16 {
11090
}
fn default_app_dir() -> PathBuf {
".".parse().expect("default app_dir pathbuf failed")
}
impl Default for Config {
fn default() -> Self {
Config {
mock_sgx: false,
tx_sender: default_tx_sender(),
chain_id: default_chain_id(),
node_url: default_node_url(),
enclave_rpc_addr: default_rpc_addr(),
enclave_rpc_port: default_port(),
app_dir: default_app_dir(),
trusted_height: u64::default(),
trusted_hash: String::default(),
}
}
}

View file

@ -9,4 +9,34 @@ pub enum Error {
PathNotFile(String),
/// unspecified error: {0}
GenericErr(String),
/// IoError: {0}
IoError(String),
/// TOML Error : {0}
TomlError(String),
/// Tendermint error: {0}
TendermintError(String),
}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
Error::IoError(err.to_string())
}
}
impl From<toml::de::Error> for Error {
fn from(err: toml::de::Error) -> Self {
Error::TomlError(err.to_string())
}
}
impl From<toml::ser::Error> for Error {
fn from(err: toml::ser::Error) -> Self {
Error::TomlError(err.to_string())
}
}
impl From<tendermint::Error> for Error {
fn from(err: tendermint::Error) -> Self {
Error::TendermintError(err.to_string())
}
}

View file

@ -1,6 +1,6 @@
use async_trait::async_trait;
use crate::{error::Error, request::Request, response::Response, Config};
use crate::{config::Config, error::Error, request::Request, response::Response};
pub mod utils;
// commands

View file

@ -4,11 +4,11 @@ use async_trait::async_trait;
use tracing::{debug, trace};
use crate::{
config::Config,
error::Error,
handler::Handler,
request::contract_build::ContractBuildRequest,
response::{contract_build::ContractBuildResponse, Response},
Config,
};
#[async_trait]

View file

@ -17,11 +17,11 @@ use super::utils::{
types::{Log, WasmdTxResponse},
};
use crate::{
config::Config,
error::Error,
handler::{utils::types::RelayMessage, Handler},
request::contract_deploy::ContractDeployRequest,
response::{contract_deploy::ContractDeployResponse, Response},
Config,
};
#[async_trait]
@ -33,11 +33,11 @@ impl Handler for ContractDeployRequest {
trace!("initializing directory structure...");
let (code_id, contract_addr) = if config.mock_sgx {
deploy::<RawMockAttestation>(self, config.mock_sgx)
deploy::<RawMockAttestation>(self, config)
.await
.map_err(|e| Error::GenericErr(e.to_string()))?
} else {
deploy::<RawEpidAttestation>(self, config.mock_sgx)
deploy::<RawEpidAttestation>(self, config)
.await
.map_err(|e| Error::GenericErr(e.to_string()))?
};
@ -52,12 +52,12 @@ impl Handler for ContractDeployRequest {
async fn deploy<DA: Serialize + DeserializeOwned>(
args: ContractDeployRequest,
mock_sgx: bool,
config: Config,
) -> Result<(u64, String), anyhow::Error> {
// TODO: Replace with call to Rust package
let relay_path = current_dir()?.join("../");
let httpurl = Url::parse(&format!("http://{}", args.node_url))?;
let httpurl = Url::parse(&format!("http://{}", config.node_url))?;
let tmrpc_client = HttpClient::new(httpurl.as_str())?;
let wasmd_client = CliWasmdClient::new(Url::parse(httpurl.as_str())?);
@ -67,8 +67,8 @@ async fn deploy<DA: Serialize + DeserializeOwned>(
// TODO: uncertain about the path -> string conversion
let deploy_output: WasmdTxResponse = serde_json::from_str(&wasmd_client.deploy(
&args.chain_id,
&args.sender,
&config.chain_id,
&config.tx_sender,
contract_path.display().to_string(),
)?)?;
let res = block_tx_commit(&tmrpc_client, deploy_output.txhash).await?;
@ -79,17 +79,18 @@ async fn deploy<DA: Serialize + DeserializeOwned>(
info!("\n🚀 Communicating with Relay to Instantiate...\n");
let raw_init_msg = run_relay::<QuartzInstantiateMsg<DA>>(
relay_path.as_path(),
mock_sgx,
config.mock_sgx,
RelayMessage::Instantiate,
)?;
)
.await?;
info!("\n🚀 Instantiating {} Contract\n", args.label);
let mut init_msg = args.init_msg;
init_msg["quartz"] = json!(raw_init_msg);
let init_output: WasmdTxResponse = serde_json::from_str(&wasmd_client.init(
&args.chain_id,
&args.sender,
&config.chain_id,
&config.tx_sender,
code_id,
json!(init_msg),
&format!("{} Contract #{}", args.label, code_id),

View file

@ -4,11 +4,11 @@ use async_trait::async_trait;
use tracing::{debug, info};
use crate::{
config::Config,
error::Error,
handler::Handler,
request::enclave_build::EnclaveBuildRequest,
response::{enclave_build::EnclaveBuildResponse, Response},
Config,
};
#[async_trait]

View file

@ -4,13 +4,12 @@ use async_trait::async_trait;
use tokio::process::Command;
use tracing::{debug, info};
use super::utils::helpers::read_hash_height;
use crate::{
config::Config,
error::Error,
handler::Handler,
handler::{utils::helpers::get_hash_height, Handler},
request::enclave_start::EnclaveStartRequest,
response::{enclave_start::EnclaveStartResponse, Response},
Config,
};
#[async_trait]
@ -18,16 +17,16 @@ impl Handler for EnclaveStartRequest {
type Error = Error;
type Response = Response;
async fn handle(self, config: Config) -> Result<Self::Response, Self::Error> {
let enclave_dir = self.app_dir.join("enclave");
let (trusted_height, trusted_hash) = read_hash_height(self.app_dir.as_path())
.await
.map_err(|e| Error::GenericErr(e.to_string()))?;
async fn handle(self, mut config: Config) -> Result<Self::Response, Self::Error> {
// Get trusted height and hash
let (trusted_height, trusted_hash) = get_hash_height(self.use_latest_trusted, &mut config)?;
let enclave_dir = config.app_dir.join("enclave");
if config.mock_sgx {
let enclave_args: Vec<String> = vec![
"--chain-id".to_string(),
self.chain_id,
config.chain_id.to_string(),
"--trusted-height".to_string(),
trusted_height.to_string(),
"--trusted-hash".to_string(),

View file

@ -17,14 +17,14 @@ use super::utils::{
types::WasmdTxResponse,
};
use crate::{
config::Config,
error::Error,
handler::{
utils::{helpers::read_hash_height, types::RelayMessage},
utils::{helpers::get_hash_height, types::RelayMessage},
Handler,
},
request::handshake::HandshakeRequest,
response::{handshake::HandshakeResponse, Response},
Config,
};
#[async_trait]
@ -36,7 +36,7 @@ impl Handler for HandshakeRequest {
trace!("starting handshake...");
// TODO: may need to import verbosity here
let pub_key = handshake(self, config.mock_sgx)
let pub_key = handshake(self, config)
.await
.map_err(|e| Error::GenericErr(e.to_string()))?;
@ -49,30 +49,32 @@ struct Message<'a> {
message: &'a str,
}
async fn handshake(args: HandshakeRequest, mock_sgx: bool) -> Result<String, anyhow::Error> {
let httpurl = Url::parse(&format!("http://{}", args.node_url))?;
let wsurl = format!("ws://{}/websocket", args.node_url);
async fn handshake(args: HandshakeRequest, mut config: Config) -> Result<String, anyhow::Error> {
let httpurl = Url::parse(&format!("http://{}", config.node_url))?;
let wsurl = format!("ws://{}/websocket", config.node_url);
let tmrpc_client = HttpClient::new(httpurl.as_str())?;
let wasmd_client = CliWasmdClient::new(Url::parse(httpurl.as_str())?);
let (trusted_height, trusted_hash) = get_hash_height(false, &mut config)?;
// TODO: dir logic issue #125
// Read trusted hash and height from files
let base_path = current_dir()?.join("../");
let trusted_files_path = args.app_dir;
let (trusted_height, trusted_hash) = read_hash_height(trusted_files_path.as_path()).await?;
info!("Running SessionCreate");
let res: serde_json::Value =
run_relay(base_path.as_path(), mock_sgx, RelayMessage::SessionCreate)?;
let res: serde_json::Value = run_relay(
base_path.as_path(),
config.mock_sgx,
RelayMessage::SessionCreate,
)
.await?;
let output: WasmdTxResponse = serde_json::from_str(
wasmd_client
.tx_execute(
&args.contract.clone(),
&args.chain_id,
&config.chain_id,
2000000,
&args.sender,
&config.tx_sender,
json!(res),
)?
.as_str(),
@ -92,7 +94,7 @@ async fn handshake(args: HandshakeRequest, mock_sgx: bool) -> Result<String, any
debug!("Proof path: {:?}", proof_path.to_str());
// Call tm prover with trusted hash and height
let config = TmProverConfig {
let prover_config = TmProverConfig {
primary: httpurl.as_str().parse()?,
witnesses: httpurl.as_str().parse()?,
trusted_height,
@ -101,11 +103,11 @@ async fn handshake(args: HandshakeRequest, mock_sgx: bool) -> Result<String, any
verbose: "1".parse()?, // TODO: both tm-prover and cli define the same Verbosity struct. Need to define this once and import
contract_address: args.contract.clone(),
storage_key: "quartz_session".to_string(),
chain_id: args.chain_id.to_string(),
chain_id: config.chain_id.to_string(),
..Default::default()
};
debug!("config: {:?}", config);
if let Err(report) = prove(config).await {
debug!("config: {:?}", prover_config);
if let Err(report) = prove(prover_config).await {
return Err(anyhow!("Tendermint prover failed. Report: {}", report));
}
@ -119,9 +121,10 @@ async fn handshake(args: HandshakeRequest, mock_sgx: bool) -> Result<String, any
info!("Running SessionSetPubKey");
let res: serde_json::Value = run_relay(
base_path.as_path(),
mock_sgx,
config.mock_sgx,
RelayMessage::SessionSetPubKey(proof_json),
)?;
)
.await?;
// Submit SessionSetPubKey to contract
let output: WasmdTxResponse = serde_json::from_str(
@ -130,7 +133,7 @@ async fn handshake(args: HandshakeRequest, mock_sgx: bool) -> Result<String, any
&args.contract.clone(),
&ChainId::from_str("testing")?,
2000000,
&args.sender,
&config.tx_sender,
json!(res),
)?
.as_str(),

View file

@ -2,14 +2,15 @@ use std::path::PathBuf;
use async_trait::async_trait;
use cargo_generate::{generate, GenerateArgs, TemplatePath, Vcs};
use tokio::fs;
use tracing::trace;
use crate::{
config::Config,
error::Error,
handler::Handler,
request::init::InitRequest,
response::{init::InitResponse, Response},
Config,
};
#[async_trait]
@ -17,13 +18,30 @@ impl Handler for InitRequest {
type Error = Error;
type Response = Response;
async fn handle(self, _config: Config) -> Result<Self::Response, Self::Error> {
async fn handle(self, config: Config) -> Result<Self::Response, Self::Error> {
trace!("initializing directory structure...");
let root_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..");
let parent = self
.name
.parent()
.map(|p| p.to_path_buf())
.expect("path already validated");
fs::create_dir_all(&parent)
.await
.map_err(|e| Error::GenericErr(e.to_string()))?;
let file_name = self
.name
.file_name()
.and_then(|f| f.to_str())
.expect("path already validated");
let wasm_pack_args = GenerateArgs {
name: Some(self.name),
name: Some(file_name.to_string()),
destination: Some(config.app_dir.join(parent)),
overwrite: true,
vcs: Some(Vcs::Git),
template_path: TemplatePath {
// git: Some("git@github.com:informalsystems/cycles-quartz.git".to_string()), // TODO: replace with public http address when open-sourced

View file

@ -1,18 +1,21 @@
use std::{path::Path, process::Command, time::Duration};
use std::{path::Path, time::Duration};
use anyhow::anyhow;
use cosmrs::{AccountId, ErrorReport};
use cycles_sync::wasmd_client::{CliWasmdClient, WasmdClient};
use regex::Regex;
use reqwest::Url;
use serde::de::DeserializeOwned;
use subtle_encoding::bech32::decode as bech32_decode;
use tendermint::{block::Height, Hash};
use tendermint_rpc::{
endpoint::tx::Response as TmTxResponse, error::ErrorDetail, Client, HttpClient,
};
use tokio::fs;
use tokio::{fs, process::Command};
use tracing::debug;
use super::types::RelayMessage;
use crate::{config::Config, error};
pub fn wasmaddr_to_id(address_str: &str) -> Result<AccountId, anyhow::Error> {
let (hr, _) = bech32_decode(address_str).map_err(|e| anyhow!(e))?;
@ -24,7 +27,7 @@ pub fn wasmaddr_to_id(address_str: &str) -> Result<AccountId, anyhow::Error> {
}
// TODO: move wrapping result with "quartz:" struct into here
pub fn run_relay<R: DeserializeOwned>(
pub async fn run_relay<R: DeserializeOwned>(
base_path: &Path,
mock_sgx: bool,
msg: RelayMessage,
@ -41,7 +44,7 @@ pub fn run_relay<R: DeserializeOwned>(
command.arg(proof);
}
let output = command.output()?;
let output = command.output().await?;
if !output.status.success() {
return Err(anyhow!("{:?}", output));
@ -87,18 +90,55 @@ pub async fn block_tx_commit(client: &HttpClient, tx: Hash) -> Result<TmTxRespon
}
}
pub async fn read_hash_height(base_path: &Path) -> Result<(Height, Hash), anyhow::Error> {
let height_path = base_path.join("trusted.height");
let trusted_height: Height = fs::read_to_string(height_path.as_path())
.await?
.trim()
.parse()?;
/// Returns the trusted hash and height
pub fn get_hash_height(
use_latest: bool,
config: &mut Config,
) -> Result<(Height, Hash), error::Error> {
if use_latest || config.trusted_height == 0 || config.trusted_hash.is_empty() {
let (trusted_height, trusted_hash) = latest_height_hash(&config.node_url)?;
config.trusted_hash = trusted_hash.to_string();
config.trusted_height = trusted_height.into();
let hash_path = base_path.join("trusted.hash");
let trusted_hash: Hash = fs::read_to_string(hash_path.as_path())
.await?
.trim()
.parse()?;
Ok((trusted_height, trusted_hash))
Ok((trusted_height, trusted_hash))
} else {
Ok((
config.trusted_height.try_into()?,
config
.trusted_hash
.parse()
.map_err(|_| error::Error::GenericErr("invalid hash".to_string()))?,
))
}
}
// Queries the chain for the latested height and hash
pub fn latest_height_hash(node_url: &String) -> Result<(Height, Hash), error::Error> {
let httpurl = Url::parse(&format!("http://{}", node_url))
.map_err(|e| error::Error::GenericErr(e.to_string()))?;
let wasmd_client = CliWasmdClient::new(httpurl);
let (trusted_height, trusted_hash) = wasmd_client
.trusted_height_hash()
.map_err(|e| error::Error::GenericErr(e.to_string()))?;
Ok((
trusted_height.try_into()?,
trusted_hash.parse().expect("invalid hash from wasmd"),
))
}
pub async fn persist_config_hash_height(config: &Config) -> Result<(), error::Error> {
let config_path = config.app_dir.join("quartz.toml");
let toml_content = fs::read_to_string(&config_path).await?;
let mut written_config: Config = toml::from_str(&toml_content)?;
written_config.trusted_hash.clone_from(&config.trusted_hash);
written_config.trusted_height = config.trusted_height;
let toml_string = toml::to_string(config)?;
fs::write(&config_path, toml_string).await?;
Ok(())
}

View file

@ -14,13 +14,22 @@
)]
pub mod cli;
pub mod config;
pub mod error;
pub mod handler;
pub mod request;
pub mod response;
use std::path::PathBuf;
use clap::Parser;
use cli::ToFigment;
use color_eyre::eyre::Result;
use config::Config;
use figment::{
providers::{Env, Format, Serialized, Toml},
Figment,
};
use tracing_subscriber::{util::SubscriberInitExt, EnvFilter};
use crate::{cli::Cli, handler::Handler, request::Request};
@ -37,17 +46,26 @@ const BANNER: &str = r"
";
pub struct Config {
pub mock_sgx: bool,
}
#[tokio::main]
async fn main() -> Result<()> {
color_eyre::install()?;
println!("{BANNER}");
let args = Cli::parse();
let args: Cli = Cli::parse();
check_path(&args.app_dir)?;
let config: Config = Figment::new()
.merge(Toml::file(
args.app_dir
.as_ref()
.unwrap_or(&PathBuf::from("."))
.join("quartz.toml"),
))
.merge(Env::prefixed("QUARTZ_"))
.merge(Serialized::defaults(&args))
.merge(args.command.to_figment())
.extract()?;
let env_filter = EnvFilter::builder()
.with_default_directive(args.verbose.to_level_filter().into())
@ -67,11 +85,7 @@ async fn main() -> Result<()> {
// Each `Request` defines an associated `Handler` (i.e. logic) and `Response`. All handlers are
// free to log to the terminal and these logs are sent to `stderr`.
let response = request
.handle(Config {
mock_sgx: args.mock_sgx,
})
.await?;
let response = request.handle(config).await?;
// `Handlers` must use `Responses` to output to `stdout`.
println!(
@ -81,3 +95,13 @@ async fn main() -> Result<()> {
Ok(())
}
fn check_path(path: &Option<PathBuf>) -> Result<(), error::Error> {
if let Some(path) = path {
if !path.is_dir() {
return Err(error::Error::PathNotDir(format!("{}", path.display())));
}
}
Ok(())
}

View file

@ -1,5 +1,3 @@
use std::{env::current_dir, path::PathBuf};
use crate::{
cli::{Command, ContractCommand, EnclaveCommand},
error::Error,
@ -32,23 +30,9 @@ impl TryFrom<Command> for Request {
fn try_from(cmd: Command) -> Result<Self, Self::Error> {
match cmd {
Command::Init { name } => Ok(InitRequest { name }.try_into()?),
Command::Handshake {
contract,
port,
sender,
chain_id,
node_url,
enclave_rpc_addr,
app_dir,
} => Ok(HandshakeRequest {
contract,
port,
sender,
chain_id,
node_url,
enclave_rpc_addr,
app_dir: Self::path_checked(app_dir)?,
Command::Init(args) => Ok(InitRequest { name: args.name }.try_into()?),
Command::Handshake(args) => Ok(HandshakeRequest {
contract: args.contract,
}
.into()),
Command::Contract { contract_command } => contract_command.try_into(),
@ -57,53 +41,33 @@ impl TryFrom<Command> for Request {
}
}
impl Request {
fn path_checked(path: Option<PathBuf>) -> Result<PathBuf, Error> {
if let Some(path) = path {
if !path.is_dir() {
return Err(Error::PathNotDir(format!("{}", path.display())));
}
Ok(path)
} else {
Ok(current_dir().map_err(|e| Error::GenericErr(e.to_string()))?)
}
}
}
impl TryFrom<ContractCommand> for Request {
type Error = Error;
fn try_from(cmd: ContractCommand) -> Result<Request, Error> {
match cmd {
ContractCommand::Deploy {
init_msg,
node_url,
chain_id,
sender,
label,
wasm_bin_path,
} => {
if !wasm_bin_path.exists() {
return Err(Error::PathNotFile(wasm_bin_path.display().to_string()));
ContractCommand::Deploy(args) => {
if !args.wasm_bin_path.exists() {
return Err(Error::PathNotFile(args.wasm_bin_path.display().to_string()));
}
Ok(ContractDeployRequest {
init_msg: serde_json::from_str(&init_msg)
init_msg: serde_json::from_str(&args.init_msg)
.map_err(|e| Error::GenericErr(e.to_string()))?,
node_url,
chain_id,
sender,
label,
wasm_bin_path,
label: args.label,
wasm_bin_path: args.wasm_bin_path,
}
.into())
}
ContractCommand::Build { manifest_path } => {
if !manifest_path.exists() {
return Err(Error::PathNotFile(manifest_path.display().to_string()));
ContractCommand::Build(args) => {
if !args.manifest_path.exists() {
return Err(Error::PathNotFile(args.manifest_path.display().to_string()));
}
Ok(ContractBuildRequest { manifest_path }.into())
Ok(ContractBuildRequest {
manifest_path: args.manifest_path,
}
.into())
}
}
}
@ -114,12 +78,12 @@ impl TryFrom<EnclaveCommand> for Request {
fn try_from(cmd: EnclaveCommand) -> Result<Request, Error> {
match cmd {
EnclaveCommand::Build { manifest_path } => {
Ok(EnclaveBuildRequest { manifest_path }.into())
EnclaveCommand::Build(args) => Ok(EnclaveBuildRequest {
manifest_path: args.manifest_path,
}
EnclaveCommand::Start { app_dir, chain_id } => Ok(EnclaveStartRequest {
app_dir: Self::path_checked(app_dir)?,
chain_id,
.into()),
EnclaveCommand::Start(args) => Ok(EnclaveStartRequest {
use_latest_trusted: args.use_latest_trusted,
}
.into()),
}

View file

@ -1,6 +1,5 @@
use std::{collections::HashMap, path::PathBuf};
use cosmrs::tendermint::chain::Id as ChainId;
use serde::{Deserialize, Serialize};
use crate::{error::Error, request::Request};
@ -8,9 +7,6 @@ use crate::{error::Error, request::Request};
#[derive(Clone, Debug)]
pub struct ContractDeployRequest {
pub init_msg: serde_json::Value,
pub node_url: String,
pub chain_id: ChainId,
pub sender: String,
pub label: String,
pub wasm_bin_path: PathBuf,
}

View file

@ -1,11 +1,8 @@
use std::path::PathBuf;
use crate::request::Request;
#[derive(Clone, Debug)]
pub struct EnclaveStartRequest {
pub app_dir: PathBuf,
pub chain_id: String,
pub use_latest_trusted: bool,
}
impl From<EnclaveStartRequest> for Request {

View file

@ -1,18 +1,10 @@
use std::path::PathBuf;
use cosmrs::{tendermint::chain::Id as ChainId, AccountId};
use cosmrs::AccountId;
use crate::request::Request;
#[derive(Clone, Debug)]
pub struct HandshakeRequest {
pub contract: AccountId,
pub port: u16,
pub sender: String,
pub chain_id: ChainId,
pub node_url: String,
pub enclave_rpc_addr: String,
pub app_dir: PathBuf,
}
impl From<HandshakeRequest> for Request {

View file

@ -1,18 +1,23 @@
use std::path::Path;
use std::path::PathBuf;
use crate::{error::Error, request::Request};
#[derive(Clone, Debug)]
pub struct InitRequest {
pub name: String,
pub name: PathBuf,
}
impl TryFrom<InitRequest> for Request {
type Error = Error;
fn try_from(request: InitRequest) -> Result<Request, Error> {
if Path::new(&request.name).iter().count() != 1 {
return Err(Error::GenericErr("App name contains path".to_string()));
if request.name.extension().is_some() {
return Err(Error::PathNotDir(format!("{}", request.name.display())));
} else if request.name.exists() {
return Err(Error::GenericErr(format!(
"Directory already exists: {}",
request.name.display()
)));
}
Ok(Request::Init(request))

View file

@ -51,6 +51,8 @@ pub trait WasmdClient {
init_msg: M,
label: &str,
) -> Result<String, Self::Error>;
fn trusted_height_hash(&self) -> Result<(u64, String), Self::Error>;
}
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
@ -227,4 +229,29 @@ impl WasmdClient for CliWasmdClient {
// TODO: find the rust type for the tx output and return that
Ok((String::from_utf8(output.stdout)?).to_string())
}
fn trusted_height_hash(&self) -> Result<(u64, String), Self::Error> {
let mut wasmd = Command::new("wasmd");
let command = wasmd.args(["--node", self.url.as_str()]).arg("status");
let output = command.output()?;
if !output.status.success() {
return Err(anyhow!("{:?}", output));
}
let query_result: serde_json::Value =
serde_json::from_slice(&output.stdout).unwrap_or_default();
let trusted_height = query_result["SyncInfo"]["latest_block_height"]
.as_u64()
.ok_or(anyhow!("Could not query height"))?;
let trusted_hash = query_result["SyncInfo"]["latest_block_hash"]
.as_str()
.ok_or(anyhow!("Could not query height"))?
.to_string();
Ok((trusted_height, trusted_hash))
}
}