feat: error reporting with eyre (#240)

Co-authored-by: hu55a1n1 <sufialhussaini@gmail.com>
This commit is contained in:
Daniel Gushchyan 2024-10-08 01:53:49 -07:00 committed by GitHub
parent 634bc6a8f3
commit df40e592c8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 322 additions and 383 deletions

1
Cargo.lock generated
View file

@ -1285,6 +1285,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"async-trait",
"color-eyre",
"cosmos-sdk-proto",
"cosmrs",
"hex",

View file

@ -1,5 +1,6 @@
use std::path::{Path, PathBuf};
use color_eyre::{eyre::eyre, Result};
use serde::{Deserialize, Serialize};
use tokio::{
fs::File,
@ -8,7 +9,7 @@ use tokio::{
use tracing::debug;
use xxhash_rust::xxh3::Xxh3;
use crate::{config::Config, error::Error};
use crate::config::Config;
const BUFFER_SIZE: usize = 16384; // 16 KB buffer
type Hash = u64;
@ -22,7 +23,7 @@ struct DeployedContract {
// Porcelain
impl Config {
pub async fn contract_has_changed(&self, file: &Path) -> Result<bool, Error> {
pub async fn contract_has_changed(&self, file: &Path) -> Result<bool> {
let cur_hash: Hash = Self::gen_hash(file).await?;
debug!("current file hash: {}", cur_hash);
@ -39,20 +40,17 @@ impl Config {
}
/// Return a hash of the given file's contents
pub async fn gen_hash(file: &Path) -> Result<Hash, Error> {
let file = File::open(file)
.await
.map_err(|e| Error::GenericErr(e.to_string()))?;
pub async fn gen_hash(file: &Path) -> Result<Hash> {
let file = File::open(file).await?;
let mut reader = BufReader::new(file);
let mut hasher = Xxh3::new();
let mut buffer = [0; BUFFER_SIZE];
loop {
let bytes_read = reader
.read(&mut buffer)
.await
.map_err(|e| Error::GenericErr(e.to_string()))?;
let bytes_read = reader.read(&mut buffer).await?;
if bytes_read == 0 {
break;
}
@ -65,7 +63,7 @@ impl Config {
Ok(hash)
}
pub async fn save_codeid_to_cache(&self, file: &Path, code_id: u64) -> Result<(), Error> {
pub async fn save_codeid_to_cache(&self, file: &Path, code_id: u64) -> Result<()> {
let contract_hash = Self::gen_hash(file).await?;
let dest = Self::to_cache_path(self, file)?;
let deployed_contract = DeployedContract {
@ -76,7 +74,7 @@ impl Config {
Self::write_to_cache(dest.as_path(), &deployed_contract).await
}
pub async fn get_cached_codeid(&self, file: &Path) -> Result<u64, Error> {
pub async fn get_cached_codeid(&self, file: &Path) -> Result<u64> {
let cache_path = Self::to_cache_path(self, file)?;
let code_id = Self::read_from_cache(cache_path.as_path()).await?.code_id;
@ -85,12 +83,16 @@ impl Config {
// Plumbing
fn to_cache_path(&self, file: &Path) -> Result<PathBuf, Error> {
fn to_cache_path(&self, file: &Path) -> Result<PathBuf> {
// Get cache filepath (".quartz/cache/example.wasm.json") from "example.wasm" filepath
let mut filename = file
.file_name()
.ok_or(Error::PathNotFile(file.display().to_string()))?
.ok_or(eyre!(
"file at cache filepath does not exist {}",
file.display()
))?
.to_os_string();
filename.push(".json");
let cached_file_path = Self::cache_dir(self)?.join::<PathBuf>(filename.into());
@ -99,42 +101,43 @@ impl Config {
}
/// Retreive hash from cache file
async fn read_from_cache(cache_file: &Path) -> Result<DeployedContract, Error> {
let content = tokio::fs::read_to_string(cache_file)
.await
.map_err(|e| Error::GenericErr(e.to_string()))?;
serde_json::from_str(&content).map_err(|e| Error::GenericErr(e.to_string()))
async fn read_from_cache(cache_file: &Path) -> Result<DeployedContract> {
let content = tokio::fs::read_to_string(cache_file).await?;
serde_json::from_str(&content).map_err(|e| eyre!(e))
}
/// Write a given file's contents hash to a file in cache directory
async fn write_to_cache(cache_file: &Path, data: &DeployedContract) -> Result<(), Error> {
let content = serde_json::to_string(data).map_err(|e| Error::GenericErr(e.to_string()))?;
async fn write_to_cache(cache_file: &Path, data: &DeployedContract) -> Result<()> {
let content = serde_json::to_string(data)?;
tokio::fs::write(cache_file, content)
.await
.map_err(|e| Error::GenericErr(e.to_string()))
.map_err(|e| eyre!(e))
}
pub fn cache_dir(&self) -> Result<PathBuf> {
let cache_dir = self.app_dir.join(".cache/");
std::fs::create_dir_all(&cache_dir)?;
Ok(cache_dir)
}
pub fn cache_dir(&self) -> Result<PathBuf, Error> {
Ok(self.app_dir.join(".cache/"))
}
pub fn build_log_dir(&self) -> Result<PathBuf, Error> {
Ok(self.app_dir.join(".cache/log/"))
pub fn build_log_dir(&self) -> Result<PathBuf> {
let build_log_dir = self.app_dir.join(".cache/log/");
std::fs::create_dir_all(&build_log_dir)?;
Ok(build_log_dir)
}
/// Creates the build log if it isn't created already, returns relative path from app_dir to log directory
pub async fn create_build_log(&self) -> Result<PathBuf, Error> {
pub async fn create_build_log(&self) -> Result<PathBuf> {
let log_dir = Self::build_log_dir(self)?;
if !log_dir.exists() {
tokio::fs::create_dir_all(&log_dir)
.await
.map_err(|e| Error::GenericErr(e.to_string()))?;
tokio::fs::create_dir_all(&log_dir).await?;
}
Ok(log_dir)
}
pub async fn log_build(&self, is_enclave: bool) -> Result<(), Error> {
pub async fn log_build(&self, is_enclave: bool) -> Result<()> {
let log_dir = Self::create_build_log(self).await?;
let filename = match is_enclave {
@ -142,9 +145,7 @@ impl Config {
false => "contract",
};
tokio::fs::write(log_dir.join(filename), "test")
.await
.map_err(|e| Error::GenericErr(e.to_string()))?;
tokio::fs::write(log_dir.join(filename), "log").await?;
Ok(())
}

View file

@ -79,7 +79,7 @@ fn default_ws_url() -> Url {
}
fn default_grpc_url() -> Url {
"http://127.0.0.1:9090,"
"http://127.0.0.1:9090"
.parse()
.expect("valid hardcoded URL")
}

View file

@ -1,58 +0,0 @@
use displaydoc::Display;
use thiserror::Error;
#[derive(Debug, Display, Error)]
pub enum Error {
/// Specified path `{0}` is not a directory
PathNotDir(String),
/// Specified file `{0}` does not exist
PathNotFile(String),
/// unspecified error: {0}
GenericErr(String),
/// IoError: {0}
IoError(String),
/// TOML Error : {0}
TomlError(String),
/// Tendermint error: {0}
TendermintError(String),
/// Clearscreen error: {0}
ClearscreenError(String),
/// JSON Error: {0}
JsonError(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())
}
}
impl From<clearscreen::Error> for Error {
fn from(err: clearscreen::Error) -> Self {
Error::ClearscreenError(err.to_string())
}
}
impl From<serde_json::Error> for Error {
fn from(err: serde_json::Error) -> Self {
Error::JsonError(err.to_string())
}
}

View file

@ -1,6 +1,7 @@
use async_trait::async_trait;
use color_eyre::{Report, Result};
use crate::{config::Config, error::Error, request::Request, response::Response};
use crate::{config::Config, request::Request, response::Response};
pub mod utils;
// commands
@ -14,24 +15,16 @@ pub mod init;
#[async_trait]
pub trait Handler {
type Error;
type Response;
async fn handle<C: AsRef<Config> + Send>(
self,
config: C,
) -> Result<Self::Response, Self::Error>;
async fn handle<C: AsRef<Config> + Send>(self, config: C) -> Result<Self::Response, Report>;
}
#[async_trait]
impl Handler for Request {
type Error = Error;
type Response = Response;
async fn handle<C: AsRef<Config> + Send>(
self,
config: C,
) -> Result<Self::Response, Self::Error> {
async fn handle<C: AsRef<Config> + Send>(self, config: C) -> Result<Self::Response, Report> {
match self {
Request::Init(request) => request.handle(config).await,
Request::Handshake(request) => request.handle(config).await,

View file

@ -1,12 +1,11 @@
use std::process::Command;
use async_trait::async_trait;
use color_eyre::owo_colors::OwoColorize;
use color_eyre::{eyre::eyre, owo_colors::OwoColorize, Report, Result};
use tracing::{debug, info};
use crate::{
config::Config,
error::Error,
handler::Handler,
request::contract_build::ContractBuildRequest,
response::{contract_build::ContractBuildResponse, Response},
@ -14,13 +13,9 @@ use crate::{
#[async_trait]
impl Handler for ContractBuildRequest {
type Error = Error;
type Response = Response;
async fn handle<C: AsRef<Config> + Send>(
self,
config: C,
) -> Result<Self::Response, Self::Error> {
async fn handle<C: AsRef<Config> + Send>(self, config: C) -> Result<Self::Response, Report> {
let config = config.as_ref();
info!("{}", "\nPeforming Contract Build".blue().bold());
@ -46,15 +41,10 @@ impl Handler for ContractBuildRequest {
}
info!("{}", "🚧 Building contract binary ...".green().bold());
let status = command
.status()
.map_err(|e| Error::GenericErr(e.to_string()))?;
let status = command.status()?;
if !status.success() {
return Err(Error::GenericErr(format!(
"Couldn't build contract. \n{:?}",
status
)));
return Err(eyre!("Couldn't build contract. \n{:?}", status));
}
config.log_build(false).await?;

View file

@ -2,7 +2,11 @@ use std::path::Path;
use async_trait::async_trait;
use cargo_metadata::MetadataCommand;
use color_eyre::owo_colors::OwoColorize;
use color_eyre::{
eyre::{eyre, Context},
owo_colors::OwoColorize,
Report, Result,
};
use cw_client::{CliClient, CwClient};
use serde_json::json;
use tendermint_rpc::HttpClient;
@ -11,7 +15,6 @@ use tracing::{debug, info};
use super::utils::{helpers::block_tx_commit, types::WasmdTxResponse};
use crate::{
config::Config,
error::Error,
handler::{utils::relay::RelayMessage, Handler},
request::contract_deploy::ContractDeployRequest,
response::{contract_deploy::ContractDeployResponse, Response},
@ -19,24 +22,18 @@ use crate::{
#[async_trait]
impl Handler for ContractDeployRequest {
type Error = Error;
type Response = Response;
async fn handle<C: AsRef<Config> + Send>(
self,
config: C,
) -> Result<Self::Response, Self::Error> {
async fn handle<C: AsRef<Config> + Send>(self, config: C) -> Result<Self::Response, Report> {
let config = config.as_ref();
info!("{}", "\nPeforming Contract Deploy".blue().bold());
// Get contract package name in snake_case
let package_name = MetadataCommand::new()
.manifest_path(&self.contract_manifest)
.exec()
.map_err(|e| Error::GenericErr(e.to_string()))?
.exec()?
.root_package()
.ok_or("No root package found in the metadata")
.map_err(|e| Error::GenericErr(e.to_string()))?
.ok_or(eyre!("No root package found in the metadata"))?
.name
.clone()
.replace('-', "_");
@ -47,9 +44,7 @@ impl Handler for ContractDeployRequest {
.join(package_name)
.with_extension("wasm");
let (code_id, contract_addr) = deploy(wasm_bin_path.as_path(), self, config)
.await
.map_err(|e| Error::GenericErr(e.to_string()))?;
let (code_id, contract_addr) = deploy(wasm_bin_path.as_path(), self, config).await?;
Ok(ContractDeployResponse {
code_id,
@ -63,17 +58,23 @@ async fn deploy(
wasm_bin_path: &Path,
args: ContractDeployRequest,
config: &Config,
) -> Result<(u64, String), anyhow::Error> {
) -> Result<(u64, String), Report> {
let tmrpc_client = HttpClient::new(config.node_url.as_str())?;
let cw_client = CliClient::neutrond(config.node_url.clone());
info!("🚀 Deploying {} Contract", args.label);
let code_id = if config.contract_has_changed(wasm_bin_path).await? {
let deploy_output: WasmdTxResponse = serde_json::from_str(&cw_client.deploy(
&config.chain_id,
&config.tx_sender,
wasm_bin_path.display().to_string(),
)?)?;
let deploy_output: WasmdTxResponse = serde_json::from_str(
&cw_client
.deploy(
&config.chain_id,
&config.tx_sender,
wasm_bin_path.display().to_string(),
)
.map_err(|err| eyre!(Box::new(err)))?,
)
.wrap_err("Error calling deploy on cw client")?;
let res = block_tx_commit(&tmrpc_client, deploy_output.txhash).await?;
// Find the 'code_id' attribute
@ -89,15 +90,21 @@ async fn deploy(
.find(|attr| attr.key_str().unwrap_or("") == "code_id")
})
.and_then(|attr| attr.value_str().ok().and_then(|v| v.parse().ok()))
.ok_or_else(|| anyhow::anyhow!("Failed to find code_id in the transaction result"))?;
.ok_or_else(|| eyre!("Failed to find code_id in the transaction result"))?;
info!("Code ID: {}", code_id);
config.save_codeid_to_cache(wasm_bin_path, code_id).await?;
config
.save_codeid_to_cache(wasm_bin_path, code_id)
.await
.wrap_err("Error saving contract code id to cache")?;
code_id
} else {
config.get_cached_codeid(wasm_bin_path).await?
config
.get_cached_codeid(wasm_bin_path)
.await
.wrap_err("Error getting contract code id from cache")?
};
info!("🚀 Communicating with Relay to Instantiate...");
@ -116,6 +123,7 @@ async fn deploy(
json!(init_msg),
&format!("{} Contract #{}", args.label, code_id),
)?)?;
let res = block_tx_commit(&tmrpc_client, init_output.txhash).await?;
// Find the '_contract_address' attribute
@ -131,9 +139,7 @@ async fn deploy(
.find(|attr| attr.key_str().unwrap_or("") == "_contract_address")
})
.and_then(|attr| attr.value_str().ok().and_then(|v| v.parse().ok()))
.ok_or_else(|| {
anyhow::anyhow!("Failed to find contract_address in the transaction result")
})?;
.ok_or_else(|| eyre!("Failed to find contract_address in the transaction result"))?;
info!("🚀 Successfully deployed and instantiated contract!");
info!("🆔 Code ID: {}", code_id);

View file

@ -1,9 +1,11 @@
use std::{path::PathBuf, time::Duration};
use async_trait::async_trait;
use color_eyre::owo_colors::OwoColorize;
// todo get rid of this?
use miette::{IntoDiagnostic, Result};
use color_eyre::{
eyre::{eyre, Context},
owo_colors::OwoColorize,
Report, Result,
};
use quartz_common::proto::core_client::CoreClient;
use tokio::{sync::mpsc, time::sleep};
use tracing::{debug, info};
@ -11,7 +13,6 @@ use watchexec::Watchexec;
use watchexec_signals::Signal;
use crate::{
error::Error,
handler::{utils::helpers::wasmaddr_to_id, Handler},
request::{
contract_build::ContractBuildRequest, contract_deploy::ContractDeployRequest,
@ -24,13 +25,9 @@ use crate::{
#[async_trait]
impl Handler for DevRequest {
type Error = Error;
type Response = Response;
async fn handle<C: AsRef<Config> + Send>(
self,
config: C,
) -> Result<Self::Response, Self::Error> {
async fn handle<C: AsRef<Config> + Send>(self, config: C) -> Result<Self::Response, Report> {
let config = config.as_ref();
info!("\nPeforming Dev");
@ -58,7 +55,7 @@ async fn dev_driver(
mut rx: mpsc::Receiver<DevRebuild>,
args: &DevRequest,
config: Config,
) -> Result<(), Error> {
) -> Result<(), Report> {
// State
let mut first_enclave_message = true;
let mut first_contract_message = true;
@ -82,7 +79,10 @@ async fn dev_driver(
let contract_build = ContractBuildRequest {
contract_manifest: args.contract_manifest.clone(),
};
contract_build.handle(&config).await?;
contract_build
.handle(&config)
.await
.wrap_err("Could not run `contract build`")?;
// Start enclave in background
spawn_enclave_start(args, &config)?;
@ -99,9 +99,7 @@ async fn dev_driver(
info!("{}", "Enclave is listening for requests...".green().bold());
}
Err(e) => {
eprintln!("Error launching quartz app");
return Err(e);
return Err(e).wrap_err("Error initializing `quartz dev`");
}
}
}
@ -132,9 +130,7 @@ async fn dev_driver(
info!("{}", "Enclave is listening for requests...".green().bold());
}
Err(e) => {
eprintln!("Error restarting enclave and handshake");
return Err(e);
return Err(e).wrap_err("Error restarting enclave after rebuild");
}
}
}
@ -154,7 +150,7 @@ async fn dev_driver(
Err(e) => {
eprintln!("Error deploying contract and handshake:");
return Err(e);
return Err(e).wrap_err("Error redeploying contract after rebuild");
}
}
@ -167,7 +163,7 @@ async fn dev_driver(
}
// Spawns enclve start in a separate task which runs in the background
fn spawn_enclave_start(args: &DevRequest, config: &Config) -> Result<(), Error> {
fn spawn_enclave_start(args: &DevRequest, config: &Config) -> Result<()> {
// In separate process, launch the enclave
let enclave_start = EnclaveStartRequest {
unsafe_trust_latest: args.unsafe_trust_latest,
@ -180,10 +176,8 @@ fn spawn_enclave_start(args: &DevRequest, config: &Config) -> Result<(), Error>
tokio::spawn(async move {
if let Err(e) = enclave_start.handle(config_cpy).await {
eprintln!("Error running enclave start.\n {}", e);
eprintln!("Error running enclave start.\n {:?}", e);
}
Ok::<(), Error>(())
});
Ok(())
@ -194,7 +188,7 @@ async fn deploy_and_handshake(
contract: Option<&str>,
args: &DevRequest,
config: &Config,
) -> Result<String, Error> {
) -> Result<String> {
info!("Waiting for enclave start to deploy contract and handshake");
// Wait at most 60 seconds to connect to enclave
@ -210,9 +204,7 @@ async fn deploy_and_handshake(
i -= 1;
if i == 0 {
return Err(Error::GenericErr(
"Could not connect to enclave".to_string(),
));
return Err(eyre!("Could not connect to enclave"));
}
}
// Calls which interact with enclave
@ -231,34 +223,33 @@ async fn deploy_and_handshake(
contract_manifest: args.contract_manifest.clone(),
};
// Call handler
let cd_res = contract_deploy.handle(config).await;
let cd_res = contract_deploy
.handle(config)
.await
.wrap_err("Could not run `quartz contract deploy`")?;
// Return contract address or shutdown enclave & error
match cd_res {
Ok(Response::ContractDeploy(res)) => res.contract_addr,
Err(e) => return Err(e),
_ => unreachable!("Unexpected response variant"),
if let Response::ContractDeploy(res) = cd_res {
res.contract_addr
} else {
unreachable!("Unexpected response variant")
}
};
// Run handshake
info!("Running handshake on contract `{}`", contract);
let handshake = HandshakeRequest {
contract: wasmaddr_to_id(&contract).map_err(|_| Error::GenericErr(String::default()))?,
contract: wasmaddr_to_id(&contract)?,
unsafe_trust_latest: args.unsafe_trust_latest,
};
let h_res = handshake.handle(config).await;
match h_res {
Ok(Response::Handshake(res)) => {
info!("Handshake complete: {}", res.pub_key);
}
Err(e) => {
return Err(e);
}
_ => unreachable!("Unexpected response variant"),
};
let h_res = handshake
.handle(config)
.await
.wrap_err("Could not run `quartz handshake`")?;
println!("got here");
if let Response::Handshake(res) = h_res {
info!("Handshake complete: {}", res.pub_key);
}
Ok(contract)
}
@ -304,7 +295,7 @@ async fn watcher(tx: mpsc::Sender<DevRebuild>, log_dir: PathBuf) -> Result<()> {
wx.config.pathset([log_dir]);
// Keep running until Watchexec quits
let _ = main.await.into_diagnostic()?;
let _ = main.await?;
Ok(())
}

View file

@ -1,11 +1,10 @@
use async_trait::async_trait;
use color_eyre::owo_colors::OwoColorize;
use color_eyre::{eyre::eyre, owo_colors::OwoColorize, Report, Result};
use tokio::process::Command;
use tracing::{debug, info};
use crate::{
config::Config,
error::Error,
handler::Handler,
request::enclave_build::EnclaveBuildRequest,
response::{enclave_build::EnclaveBuildResponse, Response},
@ -13,13 +12,9 @@ use crate::{
#[async_trait]
impl Handler for EnclaveBuildRequest {
type Error = Error;
type Response = Response;
async fn handle<C: AsRef<Config> + Send>(
self,
config: C,
) -> Result<Self::Response, Self::Error> {
async fn handle<C: AsRef<Config> + Send>(self, config: C) -> Result<Self::Response, Report> {
let config = config.as_ref();
info!("{}", "\nPeforming Enclave Build".blue().bold());
@ -43,16 +38,10 @@ impl Handler for EnclaveBuildRequest {
}
info!("{}", "🚧 Running build command ...".green().bold());
let status = command
.status()
.await
.map_err(|e| Error::GenericErr(e.to_string()))?;
let status = command.status().await?;
if !status.success() {
return Err(Error::GenericErr(format!(
"Couldn't build enclave. {:?}",
status
)));
return Err(eyre!("Couldn't build enclave. {:?}", status));
}
config.log_build(true).await?;

View file

@ -2,7 +2,11 @@ use std::{fs, path::Path};
use async_trait::async_trait;
use cargo_metadata::MetadataCommand;
use color_eyre::owo_colors::OwoColorize;
use color_eyre::{
eyre::{eyre, Context},
owo_colors::OwoColorize,
Report, Result,
};
use cosmrs::AccountId;
use quartz_common::enclave::types::Fmspc;
use reqwest::Url;
@ -12,7 +16,6 @@ use tracing::{debug, info};
use crate::{
config::Config,
error::Error,
handler::{utils::helpers::write_cache_hash_height, Handler},
request::enclave_start::EnclaveStartRequest,
response::{enclave_start::EnclaveStartResponse, Response},
@ -20,18 +23,16 @@ use crate::{
#[async_trait]
impl Handler for EnclaveStartRequest {
type Error = Error;
type Response = Response;
async fn handle<C: AsRef<Config> + Send>(
self,
config: C,
) -> Result<Self::Response, Self::Error> {
async fn handle<C: AsRef<Config> + Send>(self, config: C) -> Result<Self::Response, Report> {
let config = config.as_ref().clone();
info!("{}", "\nPeforming Enclave Start".blue().bold());
// Get trusted height and hash
let (trusted_height, trusted_hash) = self.get_hash_height(&config)?;
let (trusted_height, trusted_hash) = self
.get_hash_height(&config)
.wrap_err("Error getting trusted hash and height")?;
write_cache_hash_height(trusted_height, trusted_hash, &config).await?;
if config.mock_sgx {
@ -59,27 +60,21 @@ impl Handler for EnclaveStartRequest {
handle_process(enclave_child).await?;
} else {
let Some(fmspc) = self.fmspc else {
return Err(Error::GenericErr(
"FMSPC is required if MOCK_SGX isn't set".to_string(),
));
return Err(eyre!("FMSPC is required if MOCK_SGX isn't set"));
};
let Some(tcbinfo_contract) = self.tcbinfo_contract else {
return Err(Error::GenericErr(
"tcbinfo_contract is required if MOCK_SGX isn't set".to_string(),
));
return Err(eyre!("tcbinfo_contract is required if MOCK_SGX isn't set"));
};
let Some(dcap_verifier_contract) = self.dcap_verifier_contract else {
return Err(Error::GenericErr(
"dcap_verifier_contract is required if MOCK_SGX isn't set".to_string(),
return Err(eyre!(
"dcap_verifier_contract is required if MOCK_SGX isn't set"
));
};
if let Err(_) = std::env::var("ADMIN_SK") {
return Err(Error::GenericErr(
"ADMIN_SK environment variable is not set".to_string(),
));
if std::env::var("ADMIN_SK").is_err() {
return Err(eyre!("ADMIN_SK environment variable is not set"));
};
let enclave_dir = fs::canonicalize(config.app_dir.join("enclave"))?;
@ -119,17 +114,11 @@ impl Handler for EnclaveStartRequest {
}
}
async fn handle_process(mut child: Child) -> Result<(), Error> {
let status = child
.wait()
.await
.map_err(|e| Error::GenericErr(e.to_string()))?;
async fn handle_process(mut child: Child) -> Result<()> {
let status = child.wait().await?;
if !status.success() {
return Err(Error::GenericErr(format!(
"Couldn't build enclave. {:?}",
status
)));
return Err(eyre!("Couldn't build enclave. {:?}", status));
}
Ok(())
}
@ -138,18 +127,17 @@ async fn create_mock_enclave_child(
app_dir: &Path,
release: bool,
enclave_args: Vec<String>,
) -> Result<Child, Error> {
) -> Result<Child> {
let enclave_dir = app_dir.join("enclave");
let target_dir = app_dir.join("target");
// Use the enclave package metadata to get the path to the program binary
let package_name = MetadataCommand::new()
.manifest_path(enclave_dir.join("Cargo.toml"))
.exec()
.map_err(|e| Error::GenericErr(e.to_string()))?
.exec()?
.root_package()
.ok_or("No root package found in the metadata")
.map_err(|e| Error::GenericErr(e.to_string()))?
.map_err(|e| eyre!(e))?
.name
.clone();
@ -166,26 +154,18 @@ async fn create_mock_enclave_child(
debug!("Enclave Start Command: {:?}", command);
info!("{}", "🚧 Spawning enclave process ...".green().bold());
let child = command
.kill_on_drop(true)
.spawn()
.map_err(|e| Error::GenericErr(e.to_string()))?;
let child = command.kill_on_drop(true).spawn()?;
Ok(child)
}
async fn gramine_sgx_gen_private_key(enclave_dir: &Path) -> Result<(), Error> {
async fn gramine_sgx_gen_private_key(enclave_dir: &Path) -> Result<()> {
// Launch the gramine-sgx-gen-private-key command
Command::new("gramine-sgx-gen-private-key")
.current_dir(enclave_dir)
.output()
.await
.map_err(|e| {
Error::GenericErr(format!(
"Failed to execute gramine-sgx-gen-private-key: {}",
e
))
})?;
.map_err(|e| eyre!("Failed to execute gramine-sgx-gen-private-key: {}", e))?;
// Continue regardless of error
// > /dev/null 2>&1 || : # may fail
@ -205,7 +185,7 @@ async fn gramine_manifest(
node_url: &Url,
ws_url: &Url,
grpc_url: &Url,
) -> Result<(), Error> {
) -> Result<()> {
let host = target_lexicon::HOST;
let arch_libdir = format!(
"/lib/{}-{}-{}",
@ -214,7 +194,7 @@ async fn gramine_manifest(
let ra_client_spid = "51CAF5A48B450D624AEFE3286D314894";
let home_dir = dirs::home_dir()
.ok_or(Error::GenericErr("home dir not set".to_string()))?
.ok_or_else(|| eyre!("Home directory not set"))?
.display()
.to_string();
@ -243,19 +223,19 @@ async fn gramine_manifest(
.current_dir(enclave_dir)
.status()
.await
.map_err(|e| Error::GenericErr(e.to_string()))?;
.map_err(|e| eyre!("Failed to execute gramine-manifest: {}", e))?;
if !status.success() {
return Err(Error::GenericErr(format!(
"Couldn't run gramine manifest. {:?}",
return Err(eyre!(
"gramine-manifest command failed with status: {:?}",
status
)));
));
}
Ok(())
}
async fn gramine_sgx_sign(enclave_dir: &Path) -> Result<(), Error> {
async fn gramine_sgx_sign(enclave_dir: &Path) -> Result<()> {
let status = Command::new("gramine-sgx-sign")
.arg("--manifest")
.arg("quartz.manifest")
@ -264,26 +244,27 @@ async fn gramine_sgx_sign(enclave_dir: &Path) -> Result<(), Error> {
.current_dir(enclave_dir)
.status()
.await
.map_err(|e| Error::GenericErr(e.to_string()))?;
.map_err(|e| eyre!("Failed to execute gramine-sgx-sign: {}", e))?;
if !status.success() {
return Err(Error::GenericErr(format!(
"gramine-sgx-sign command failed. {:?}",
return Err(eyre!(
"gramine-sgx-sign command failed with status: {:?}",
status
)));
));
}
Ok(())
}
async fn create_gramine_sgx_child(enclave_dir: &Path) -> Result<Child, Error> {
async fn create_gramine_sgx_child(enclave_dir: &Path) -> Result<Child> {
info!("🚧 Spawning enclave process ...");
let child = Command::new("gramine-sgx")
.arg("./quartz")
.kill_on_drop(true)
.current_dir(enclave_dir)
.spawn()?;
.spawn()
.map_err(|e| eyre!("Failed to spawn gramine-sgx child process: {}", e))?;
Ok(child)
}

View file

@ -1,6 +1,5 @@
use anyhow::anyhow;
use async_trait::async_trait;
use color_eyre::owo_colors::OwoColorize;
use color_eyre::{eyre::eyre, owo_colors::OwoColorize, Report, Result};
use cw_client::{CliClient, CwClient};
use futures_util::stream::StreamExt;
use quartz_tm_prover::{config::Config as TmProverConfig, prover::prove};
@ -11,7 +10,6 @@ use tracing::{debug, info};
use super::utils::{helpers::block_tx_commit, types::WasmdTxResponse};
use crate::{
config::Config,
error::Error,
handler::{
utils::{helpers::read_cached_hash_height, relay::RelayMessage},
Handler,
@ -22,27 +20,21 @@ use crate::{
#[async_trait]
impl Handler for HandshakeRequest {
type Error = Error;
type Response = Response;
async fn handle<C: AsRef<Config> + Send>(
self,
config: C,
) -> Result<Self::Response, Self::Error> {
async fn handle<C: AsRef<Config> + Send>(self, config: C) -> Result<Self::Response, Report> {
let config = config.as_ref().clone();
info!("{}", "\nPeforming Handshake".blue().bold());
// TODO: may need to import verbosity here
let pub_key = handshake(self, config)
.await
.map_err(|e| Error::GenericErr(e.to_string()))?;
let pub_key = handshake(self, config).await?;
Ok(HandshakeResponse { pub_key }.into())
}
}
async fn handshake(args: HandshakeRequest, config: Config) -> Result<String, anyhow::Error> {
async fn handshake(args: HandshakeRequest, config: Config) -> Result<String> {
let tmrpc_client = HttpClient::new(config.node_url.as_str())?;
let cw_client = CliClient::neutrond(config.node_url.clone());
@ -64,7 +56,8 @@ async fn handshake(args: HandshakeRequest, config: Config) -> Result<String, any
json!(res),
"11000untrn",
)
.await?
.await
.map_err(|err| eyre!(Box::new(err)))?// TODO: change
.as_str(),
)?;
debug!("\n\n SessionCreate tx output: {:?}", output);
@ -92,7 +85,7 @@ async fn handshake(args: HandshakeRequest, config: Config) -> Result<String, any
let proof_output = prove(prover_config)
.await
.map_err(|report| anyhow!("Tendermint prover failed. Report: {}", report))?;
.map_err(|report| eyre!("Tendermint prover failed. Report: {}", report))?;
// Execute SessionSetPubKey on enclave
info!("Running SessionSetPubKey");
@ -113,7 +106,8 @@ async fn handshake(args: HandshakeRequest, config: Config) -> Result<String, any
json!(res),
"11000untrn",
)
.await?
.await
.map_err(|err| eyre!(Box::new(err)))? // todo change
.as_str(),
)?;
@ -121,7 +115,9 @@ async fn handshake(args: HandshakeRequest, config: Config) -> Result<String, any
block_tx_commit(&tmrpc_client, output.txhash).await?;
info!("SessionSetPubKey tx committed");
let output: WasmdTxResponse = cw_client.query_tx(&output.txhash.to_string())?;
let output: WasmdTxResponse = cw_client
.query_tx(&output.txhash.to_string())
.map_err(|err| eyre!(Box::new(err)))?; // todo change
let wasm_event = output
.events
@ -136,11 +132,11 @@ async fn handshake(args: HandshakeRequest, config: Config) -> Result<String, any
}) {
Ok(pubkey.value_str()?.to_string())
} else {
Err(anyhow!("Failed to find pubkey from SetPubKey message"))
Err(eyre!("Failed to find pubkey from SetPubKey message"))
}
}
async fn two_block_waitoor(wsurl: &str) -> Result<(), anyhow::Error> {
async fn two_block_waitoor(wsurl: &str) -> Result<()> {
let (client, driver) = WebSocketClient::new(wsurl).await?;
let driver_handle = tokio::spawn(async move { driver.run().await });

View file

@ -2,13 +2,12 @@ use std::path::PathBuf;
use async_trait::async_trait;
use cargo_generate::{generate, GenerateArgs, TemplatePath, Vcs};
use color_eyre::owo_colors::OwoColorize;
use color_eyre::{eyre::Context, owo_colors::OwoColorize, Report, Result};
use tokio::fs;
use tracing::info;
use crate::{
config::Config,
error::Error,
handler::Handler,
request::init::InitRequest,
response::{init::InitResponse, Response},
@ -16,14 +15,10 @@ use crate::{
#[async_trait]
impl Handler for InitRequest {
type Error = Error;
type Response = Response;
// TODO: Add non-template init method
async fn handle<C: AsRef<Config> + Send>(
self,
config: C,
) -> Result<Self::Response, Self::Error> {
async fn handle<C: AsRef<Config> + Send>(self, config: C) -> Result<Self::Response, Report> {
let config = config.as_ref();
info!("{}", "\nPeforming Init".blue().bold());
@ -36,7 +31,7 @@ impl Handler for InitRequest {
.expect("path already validated");
fs::create_dir_all(&parent)
.await
.map_err(|e| Error::GenericErr(e.to_string()))?;
.wrap_err("Error creating directories to target app folder")?;
let file_name = self
.name

View file

@ -1,6 +1,9 @@
use std::time::Duration;
use anyhow::anyhow;
use color_eyre::{
eyre::{eyre, WrapErr},
Result,
};
use cosmrs::{AccountId, ErrorReport};
use cw_client::{CliClient, CwClient};
use regex::Regex;
@ -13,15 +16,15 @@ use tendermint_rpc::{
use tokio::fs::{self};
use tracing::debug;
use crate::{config::Config, error::Error};
use crate::config::Config;
pub fn wasmaddr_to_id(address_str: &str) -> Result<AccountId, anyhow::Error> {
let _ = bech32_decode(address_str).map_err(|e| anyhow!(e))?;
address_str.parse().map_err(|e: ErrorReport| anyhow!(e))
pub fn wasmaddr_to_id(address_str: &str) -> Result<AccountId> {
let _ = bech32_decode(address_str).map_err(|e| eyre!(e))?;
address_str.parse().map_err(|e: ErrorReport| eyre!(e))
}
// Note: time until tx commit is empiraclly 800ms on DO wasmd chain.
pub async fn block_tx_commit(client: &HttpClient, tx: Hash) -> Result<TmTxResponse, anyhow::Error> {
pub async fn block_tx_commit(client: &HttpClient, tx: Hash) -> Result<TmTxResponse> {
let re = Regex::new(r"tx \([A-F0-9]{64}\) not found")?;
tokio::time::sleep(Duration::from_millis(400)).await;
@ -35,7 +38,7 @@ pub async fn block_tx_commit(client: &HttpClient, tx: Hash) -> Result<TmTxRespon
match e.0 {
ErrorDetail::Response(subdetail) => {
if !re.is_match(subdetail.source.data().unwrap_or_default()) {
return Err(anyhow!(
return Err(eyre!(
"Error querying for tx: {}",
ErrorDetail::Response(subdetail)
));
@ -46,7 +49,7 @@ pub async fn block_tx_commit(client: &HttpClient, tx: Hash) -> Result<TmTxRespon
}
}
_ => {
return Err(anyhow!("Error querying for tx: {}", e.0));
return Err(eyre!("Error querying for tx: {}", e.0));
}
}
}
@ -55,12 +58,13 @@ pub async fn block_tx_commit(client: &HttpClient, tx: Hash) -> Result<TmTxRespon
}
// Queries the chain for the latested height and hash
pub fn query_latest_height_hash(node_url: Url) -> Result<(Height, Hash), Error> {
pub fn query_latest_height_hash(node_url: Url) -> Result<(Height, Hash)> {
let cw_client = CliClient::neutrond(node_url);
let (trusted_height, trusted_hash) = cw_client
.trusted_height_hash()
.map_err(|e| Error::GenericErr(e.to_string()))?;
.map_err(|e| eyre!(e))
.wrap_err("Could not query chain with cw client")?;
Ok((
trusted_height.try_into()?,
@ -72,7 +76,7 @@ pub async fn write_cache_hash_height(
trusted_height: Height,
trusted_hash: Hash,
config: &Config,
) -> Result<(), Error> {
) -> Result<()> {
let height_path = config.cache_dir()?.join("trusted.height");
fs::write(height_path.as_path(), trusted_height.to_string()).await?;
let hash_path = config.cache_dir()?.join("trusted.hash");
@ -81,15 +85,21 @@ pub async fn write_cache_hash_height(
Ok(())
}
pub async fn read_cached_hash_height(config: &Config) -> Result<(Height, Hash), Error> {
pub async fn read_cached_hash_height(config: &Config) -> Result<(Height, Hash)> {
let height_path = config.cache_dir()?.join("trusted.height");
let hash_path = config.cache_dir()?.join("trusted.hash");
if !height_path.exists() {
return Err(Error::PathNotFile(height_path.display().to_string()));
return Err(eyre!(
"Could not read trusted height from cache: {}",
height_path.display().to_string()
));
}
if !hash_path.exists() {
return Err(Error::PathNotFile(hash_path.display().to_string()));
return Err(eyre!(
"Could not read trusted hash from cache: {}",
hash_path.display().to_string()
));
}
let trusted_height: Height = fs::read_to_string(height_path.as_path()).await?.parse()?;

View file

@ -1,11 +1,10 @@
use color_eyre::{eyre::eyre, Result};
use quartz_common::proto::{
core_client::CoreClient, InstantiateRequest, SessionCreateRequest, SessionSetPubKeyRequest,
};
use quartz_tm_prover::config::ProofOutput;
use serde_json::{json, Value as JsonValue};
use crate::error::Error;
#[derive(Debug)]
pub enum RelayMessage {
Instantiate { init_msg: JsonValue },
@ -14,17 +13,25 @@ pub enum RelayMessage {
}
impl RelayMessage {
pub async fn run_relay(self, enclave_rpc: String) -> Result<JsonValue, Error> {
pub async fn run_relay(self, enclave_rpc: String) -> Result<JsonValue> {
// Query the gRPC quartz enclave service
let mut qc_client = CoreClient::connect(enclave_rpc)
.await
.map_err(|e| Error::GenericErr(e.to_string()))?;
let mut qc_client = CoreClient::connect(enclave_rpc).await.map_err(|e| {
eyre!(
"Failed to connect to the gRPC quartz enclave service: {}",
e
)
})?;
let attested_msg = match self {
RelayMessage::Instantiate { mut init_msg } => qc_client
.instantiate(tonic::Request::new(InstantiateRequest {}))
.await
.map_err(|e| Error::GenericErr(e.to_string()))
.map_err(|e| {
eyre!(
"Failed to instantiate via gRPC quartz enclave service: {}",
e
)
})
.map(|res| serde_json::from_str::<JsonValue>(&res.into_inner().message))?
.map(|msg| {
init_msg["quartz"] = msg;
@ -33,7 +40,12 @@ impl RelayMessage {
RelayMessage::SessionCreate => qc_client
.session_create(tonic::Request::new(SessionCreateRequest {}))
.await
.map_err(|e| Error::GenericErr(e.to_string()))
.map_err(|e| {
eyre!(
"Failed to create session via gRPC quartz enclave service: {}",
e
)
})
.map(|res| serde_json::from_str::<JsonValue>(&res.into_inner().message))?
.map(|msg| json!({ "quartz": {"session_create": msg}}).to_string())?,
RelayMessage::SessionSetPubKey { proof } => qc_client
@ -41,7 +53,12 @@ impl RelayMessage {
message: serde_json::to_string(&proof)?,
})
.await
.map_err(|e| Error::GenericErr(e.to_string()))
.map_err(|e| {
eyre!(
"Failed to set public key via gRPC quartz enclave service: {}",
e
)
})
.map(|res| serde_json::from_str::<JsonValue>(&res.into_inner().message))?
.map(|msg| json!({ "quartz": {"session_set_pub_key": msg}}).to_string())?,
};

View file

@ -16,7 +16,6 @@
pub mod cache;
pub mod cli;
pub mod config;
pub mod error;
pub mod handler;
pub mod request;
pub mod response;
@ -25,7 +24,10 @@ use std::path::PathBuf;
use clap::Parser;
use cli::ToFigment;
use color_eyre::{eyre::Result, owo_colors::OwoColorize};
use color_eyre::{
eyre::{eyre, Result},
owo_colors::OwoColorize,
};
use config::Config;
use figment::{
providers::{Env, Format, Serialized, Toml},
@ -97,10 +99,10 @@ async fn main() -> Result<()> {
Ok(())
}
fn check_path(path: &Option<PathBuf>) -> Result<(), error::Error> {
fn check_path(path: &Option<PathBuf>) -> Result<()> {
if let Some(path) = path {
if !path.is_dir() {
return Err(error::Error::PathNotDir(format!("{}", path.display())));
return Err(eyre!("Path is not a directory: {}", path.display()));
}
}

View file

@ -1,6 +1,7 @@
use color_eyre::{eyre::eyre, Report, Result};
use crate::{
cli::{Command, ContractCommand, EnclaveCommand},
error::Error,
request::{
contract_build::ContractBuildRequest, contract_deploy::ContractDeployRequest,
dev::DevRequest, enclave_build::EnclaveBuildRequest, enclave_start::EnclaveStartRequest,
@ -28,7 +29,7 @@ pub enum Request {
}
impl TryFrom<Command> for Request {
type Error = Error;
type Error = Report;
fn try_from(cmd: Command) -> Result<Self, Self::Error> {
match cmd {
@ -40,38 +41,46 @@ impl TryFrom<Command> for Request {
.into()),
Command::Contract { contract_command } => contract_command.try_into(),
Command::Enclave { enclave_command } => enclave_command.try_into(),
Command::Dev(args) => Ok(DevRequest {
watch: args.watch,
unsafe_trust_latest: args.unsafe_trust_latest,
init_msg: serde_json::from_str(&args.contract_deploy.init_msg)
.map_err(|e| Error::GenericErr(e.to_string()))?,
label: args.contract_deploy.label,
contract_manifest: args.contract_deploy.contract_manifest,
release: args.enclave_build.release,
fmspc: args.fmspc,
tcbinfo_contract: args.tcbinfo_contract,
dcap_verifier_contract: args.dcap_verifier_contract,
Command::Dev(args) => {
if !args.contract_deploy.contract_manifest.exists() {
return Err(eyre!(
"The contract manifest file does not exist: {}",
args.contract_deploy.contract_manifest.display()
));
}
Ok(DevRequest {
watch: args.watch,
unsafe_trust_latest: args.unsafe_trust_latest,
contract_manifest: args.contract_deploy.contract_manifest,
init_msg: serde_json::from_str(&args.contract_deploy.init_msg)?,
label: args.contract_deploy.label,
release: args.enclave_build.release,
fmspc: args.fmspc,
tcbinfo_contract: args.tcbinfo_contract,
dcap_verifier_contract: args.dcap_verifier_contract,
}
.into())
}
.into()),
}
}
}
impl TryFrom<ContractCommand> for Request {
type Error = Error;
type Error = Report;
fn try_from(cmd: ContractCommand) -> Result<Request, Error> {
fn try_from(cmd: ContractCommand) -> Result<Request> {
match cmd {
ContractCommand::Deploy(args) => {
if !args.contract_manifest.exists() {
return Err(Error::PathNotFile(
args.contract_manifest.display().to_string(),
return Err(eyre!(
"The contract manifest file does not exist: {}",
args.contract_manifest.display()
));
}
Ok(ContractDeployRequest {
init_msg: serde_json::from_str(&args.init_msg)
.map_err(|e| Error::GenericErr(e.to_string()))?,
init_msg: serde_json::from_str(&args.init_msg)?,
label: args.label,
contract_manifest: args.contract_manifest,
}
@ -79,8 +88,9 @@ impl TryFrom<ContractCommand> for Request {
}
ContractCommand::Build(args) => {
if !args.contract_manifest.exists() {
return Err(Error::PathNotFile(
args.contract_manifest.display().to_string(),
return Err(eyre!(
"The contract manifest file does not exist: {}",
args.contract_manifest.display()
));
}
@ -94,9 +104,9 @@ impl TryFrom<ContractCommand> for Request {
}
impl TryFrom<EnclaveCommand> for Request {
type Error = Error;
type Error = Report;
fn try_from(cmd: EnclaveCommand) -> Result<Request, Error> {
fn try_from(cmd: EnclaveCommand) -> Result<Request> {
match cmd {
EnclaveCommand::Build(_) => Ok(EnclaveBuildRequest {}.into()),
EnclaveCommand::Start(args) => Ok(EnclaveStartRequest {

View file

@ -1,8 +1,9 @@
use std::{collections::HashMap, path::PathBuf};
use color_eyre::{eyre::Context, Result};
use serde::{Deserialize, Serialize};
use crate::{error::Error, request::Request};
use crate::request::Request;
#[derive(Clone, Debug)]
pub struct ContractDeployRequest {
@ -18,10 +19,9 @@ impl From<ContractDeployRequest> for Request {
}
impl ContractDeployRequest {
pub fn checked_init(init_msg: String) -> Result<GenericQuartzInit, Error> {
let parsed: GenericQuartzInit = serde_json::from_str(&init_msg).map_err(|_| {
Error::GenericErr("Init message doesn't contain mandatory quartz field.".to_string())
})?;
pub fn checked_init(init_msg: String) -> Result<GenericQuartzInit> {
let parsed: GenericQuartzInit = serde_json::from_str(&init_msg)
.wrap_err("Init message doesn't contain mandatory quartz field")?;
Ok(parsed)
}

View file

@ -1,12 +1,10 @@
use color_eyre::Result;
use cosmrs::AccountId;
use quartz_common::enclave::types::Fmspc;
use tendermint::{block::Height, Hash};
use tracing::debug;
use crate::{
config::Config, error::Error, handler::utils::helpers::query_latest_height_hash,
request::Request,
};
use crate::{config::Config, handler::utils::helpers::query_latest_height_hash, request::Request};
#[derive(Clone, Debug)]
pub struct EnclaveStartRequest {
@ -24,7 +22,7 @@ impl From<EnclaveStartRequest> for Request {
impl EnclaveStartRequest {
/// Returns the trusted hash and height
pub fn get_hash_height(&self, config: &Config) -> Result<(Height, Hash), Error> {
pub fn get_hash_height(&self, config: &Config) -> Result<(Height, Hash)> {
if self.unsafe_trust_latest || config.trusted_height == 0 || config.trusted_hash.is_empty()
{
debug!("querying latest trusted hash & height from node");
@ -35,10 +33,7 @@ impl EnclaveStartRequest {
debug!("reusing config trusted hash & height");
Ok((
config.trusted_height.try_into()?,
config
.trusted_hash
.parse()
.map_err(|_| Error::GenericErr("invalid hash".to_string()))?,
config.trusted_hash.parse()?,
))
}
}

View file

@ -1,6 +1,8 @@
use std::path::PathBuf;
use crate::{error::Error, request::Request};
use color_eyre::{eyre::eyre, Report, Result};
use crate::request::Request;
#[derive(Clone, Debug)]
pub struct InitRequest {
@ -8,16 +10,16 @@ pub struct InitRequest {
}
impl TryFrom<InitRequest> for Request {
type Error = Error;
type Error = Report;
fn try_from(request: InitRequest) -> Result<Request, Error> {
fn try_from(request: InitRequest) -> Result<Request> {
if request.name.extension().is_some() {
return Err(Error::PathNotDir(format!("{}", request.name.display())));
return Err(eyre!("Path is not a directory: {}", request.name.display()));
} else if request.name.exists() {
return Err(Error::GenericErr(format!(
return Err(eyre!(
"Directory already exists: {}",
request.name.display()
)));
));
}
Ok(Request::Init(request))

View file

@ -12,12 +12,13 @@ authors.workspace = true
path = "src/lib.rs"
[dependencies]
anyhow.workspace = true
async-trait.workspace = true
color-eyre.workspace = true
hex.workspace = true
reqwest.workspace = true
serde.workspace = true
serde_json.workspace = true
anyhow.workspace = true
tonic.workspace = true
cosmrs = { workspace = true, default-features = false, features = ["cosmwasm"] }

View file

@ -1,6 +1,6 @@
use std::process::Command;
use anyhow::anyhow;
use color_eyre::{eyre::eyre, Help, Report, Result};
use cosmrs::{tendermint::chain::Id, AccountId};
use reqwest::Url;
use serde::de::DeserializeOwned;
@ -55,8 +55,23 @@ impl CliClient {
}
}
fn new_command(&self) -> Command {
Command::new(self.kind.bin())
fn new_command(&self) -> Result<Command> {
let bin = self.kind.bin();
if !self.is_bin_available(&bin) {
return Err(eyre!("Binary '{}' not found in PATH", bin)).suggestion(format!(
"Have you installed {}? If so, check that it's in your PATH.",
bin
));
}
Ok(Command::new(self.kind.bin()))
}
fn is_bin_available(&self, bin: &str) -> bool {
Command::new("which")
.arg(bin)
.output()
.map(|output| output.status.success())
.unwrap_or(false)
}
}
@ -66,14 +81,14 @@ impl CwClient for CliClient {
type Query = serde_json::Value;
type RawQuery = String;
type ChainId = Id;
type Error = anyhow::Error;
type Error = Report;
async fn query_smart<R: DeserializeOwned + Send>(
&self,
contract: &Self::Address,
query: Self::Query,
) -> Result<R, Self::Error> {
let mut command = self.new_command();
let mut command = self.new_command()?;
let command = command
.args(["--node", self.url.as_str()])
.args(["query", "wasm"])
@ -83,11 +98,11 @@ impl CwClient for CliClient {
let output = command.output()?;
if !output.status.success() {
return Err(anyhow!("{:?}", output));
return Err(eyre!("{:?}", output));
}
let query_result: R = serde_json::from_slice(&output.stdout)
.map_err(|e| anyhow!("Error deserializing: {}", e))?;
.map_err(|e| eyre!("Error deserializing: {}", e))?;
Ok(query_result)
}
@ -96,7 +111,7 @@ impl CwClient for CliClient {
contract: &Self::Address,
query: Self::RawQuery,
) -> Result<R, Self::Error> {
let mut command = self.new_command();
let mut command = self.new_command()?;
let command = command
.args(["--node", self.url.as_str()])
.args(["query", "wasm"])
@ -106,7 +121,7 @@ impl CwClient for CliClient {
let output = command.output()?;
if !output.status.success() {
return Err(anyhow!("{:?}", output));
return Err(eyre!("{:?}", output));
}
let query_result: R = serde_json::from_slice(&output.stdout).unwrap_or_default();
@ -114,7 +129,7 @@ impl CwClient for CliClient {
}
fn query_tx<R: DeserializeOwned + Default>(&self, txhash: &str) -> Result<R, Self::Error> {
let mut command = self.new_command();
let mut command = self.new_command()?;
let command = command
.args(["--node", self.url.as_str()])
.args(["query", "tx"])
@ -123,7 +138,7 @@ impl CwClient for CliClient {
let output = command.output()?;
if !output.status.success() {
return Err(anyhow!("{:?}", output));
return Err(eyre!("{:?}", output));
}
let query_result: R = serde_json::from_slice(&output.stdout).unwrap_or_default();
@ -139,7 +154,7 @@ impl CwClient for CliClient {
msg: M,
fees: &str,
) -> Result<String, Self::Error> {
let mut command = self.new_command();
let mut command = self.new_command()?;
let command = command
.args(["--node", self.url.as_str()])
.args(["--chain-id", chain_id.as_ref()])
@ -154,7 +169,7 @@ impl CwClient for CliClient {
let output = command.output()?;
if !output.status.success() {
return Err(anyhow!("{:?}", output));
return Err(eyre!("{:?}", output));
}
// TODO: find the rust type for the tx output and return that
@ -167,7 +182,7 @@ impl CwClient for CliClient {
sender: &str,
wasm_path: M,
) -> Result<String, Self::Error> {
let mut command = self.new_command();
let mut command = self.new_command()?;
let command = command
.args(["--node", self.url.as_str()])
.args(["tx", "wasm", "store", &wasm_path.to_string()])
@ -182,7 +197,7 @@ impl CwClient for CliClient {
let output = command.output()?;
if !output.status.success() {
return Err(anyhow!("{:?}", output));
return Err(eyre!("{:?}", output));
}
// TODO: find the rust type for the tx output and return that
@ -197,7 +212,7 @@ impl CwClient for CliClient {
init_msg: M,
label: &str,
) -> Result<String, Self::Error> {
let mut command = self.new_command();
let mut command = self.new_command()?;
let command = command
.args(["--node", self.url.as_str()])
.args(["tx", "wasm", "instantiate"])
@ -215,7 +230,7 @@ impl CwClient for CliClient {
let output = command.output()?;
if !output.status.success() {
return Err(anyhow!("{:?}", output));
return Err(eyre!("{:?}", output));
}
// TODO: find the rust type for the tx output and return that
@ -223,13 +238,13 @@ impl CwClient for CliClient {
}
fn trusted_height_hash(&self) -> Result<(u64, String), Self::Error> {
let mut command = self.new_command();
let mut command = self.new_command()?;
let command = command.args(["--node", self.url.as_str()]).arg("status");
let output = command.output()?;
if !output.status.success() {
return Err(anyhow!("{:?}", output));
return Err(eyre!("{:?}", output));
}
let query_result: serde_json::Value =
@ -241,13 +256,13 @@ impl CwClient for CliClient {
};
let trusted_height = query_result[sync_info]["latest_block_height"]
.as_str()
.ok_or(anyhow!("Could not query height"))?;
.ok_or(eyre!("Could not query height"))?;
let trusted_height = trusted_height.parse::<u64>()?;
let trusted_hash = query_result[sync_info]["latest_block_hash"]
.as_str()
.ok_or(anyhow!("Could not query height"))?
.ok_or(eyre!("Could not query height"))?
.to_string();
Ok((trusted_height, trusted_hash))

View file

@ -950,6 +950,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"async-trait",
"color-eyre",
"cosmos-sdk-proto",
"cosmrs",
"hex",

View file

@ -41,7 +41,8 @@ use crate::wslistener::WsListener;
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = Cli::parse();
let admin_sk = std::env::var("ADMIN_SK")?;
let admin_sk = std::env::var("ADMIN_SK")
.map_err(|_| anyhow::anyhow!("Admin secret key not found in env vars"))?;
let light_client_opts = LightClientOpts::new(
args.chain_id.clone(),