diff --git a/Cargo.lock b/Cargo.lock index a336df4..a751d40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -144,6 +144,9 @@ name = "anyhow" version = "1.0.88" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e1496f8fb1fbf272686b8d37f523dab3e4a7443300055e74cdaa449f3114356" +dependencies = [ + "backtrace", +] [[package]] name = "anymap2" @@ -462,7 +465,7 @@ dependencies = [ "rustversion", "serde", "sync_wrapper 1.0.1", - "tower", + "tower 0.4.13", "tower-layer", "tower-service", ] @@ -2656,7 +2659,7 @@ dependencies = [ "pin-project-lite", "socket2", "tokio", - "tower", + "tower 0.4.13", "tower-service", "tracing", ] @@ -3316,6 +3319,9 @@ dependencies = [ name = "mtcs-enclave" version = "0.1.0" dependencies = [ + "anyhow", + "async-trait", + "base64 0.22.1", "clap", "color-eyre", "cosmrs", @@ -3323,20 +3329,24 @@ dependencies = [ "cw-multi-test", "cw-tee-mtcs", "ecies", + "futures-util", "hex", "k256", "mtcs", "prost 0.13.2", "quartz-common", + "reqwest 0.12.7", "serde", "serde_json", "tendermint 0.38.1", "tendermint-light-client", + "tendermint-rpc", "thiserror", "tokio", "tonic", "tonic-build", "uuid", + "wasmd-client", ] [[package]] @@ -4087,7 +4097,6 @@ dependencies = [ "hex", "k256", "miette", - "mtcs-enclave", "once_cell", "prost 0.13.2", "quartz-common", @@ -4147,12 +4156,15 @@ dependencies = [ name = "quartz-enclave" version = "0.1.0" dependencies = [ + "anyhow", + "async-trait", "clap", "color-eyre", "cosmrs", "cosmwasm-std", "cw-proof", "ecies", + "futures-util", "hex", "k256", "mtcs", @@ -4165,9 +4177,12 @@ dependencies = [ "sha2 0.10.8", "tendermint 0.38.1", "tendermint-light-client", + "tendermint-rpc", + "thiserror", "tm-stateless-verifier", "tokio", "tonic", + "tower 0.5.1", ] [[package]] @@ -5750,7 +5765,7 @@ dependencies = [ "socket2", "tokio", "tokio-stream", - "tower", + "tower 0.4.13", "tower-layer", "tower-service", "tracing", @@ -5789,6 +5804,16 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f" +dependencies = [ + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.3" diff --git a/Cargo.toml b/Cargo.toml index 6315cf5..2d312e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ authors = ["Informal Systems "] [workspace.dependencies] # external -anyhow = { version = "1.0.86", default-features = false } +anyhow = { version = "1.0.86", features = ["std", "backtrace"] } async-trait = { version = "0.1.79", default-features = false } bip32 = { version = "0.5.1", default-features = false, features = ["alloc", "secp256k1", "bip39"] } cargo-generate = "0.21.3" @@ -32,6 +32,7 @@ der = { version = "0.7.9", default-features = false } displaydoc = { version = "0.2.4", default-features = false } ecies = { version = "0.2.3", default-features = false, features = ["pure"] } futures = { version = "0.3.27", default-features = false, features = ["alloc"] } +futures-util = { version = "0.3.30" } hex = { version = "0.4.3", default-features = false } hex-literal = { version = "0.4.1", default-features = false } k256 = { version = "0.13.2", default-features = false, features = ["ecdsa", "alloc"] } @@ -50,6 +51,7 @@ thiserror = { version = "1.0.49", default-features = false } tokio = { version = "=1.39.2", default-features = false, features = ["macros", "rt"] } tonic = { version = "=0.12.1", default-features = false, features = ["codegen", "prost", "transport"] } tonic-build = { version = "=0.12.1", default-features = false, features = ["prost", "transport"] } +tower = { version = "0.5.0" } tracing = { version = "0.1.39", default-features = false } tracing-subscriber = { version = "0.3.17", default-features = false, features = ["fmt"] } uuid = { version = "1.4.1", default-features = false, features = ["serde"] } diff --git a/apps/mtcs/enclave/Cargo.toml b/apps/mtcs/enclave/Cargo.toml index 95659f5..52c29b6 100644 --- a/apps/mtcs/enclave/Cargo.toml +++ b/apps/mtcs/enclave/Cargo.toml @@ -20,6 +20,7 @@ mock-sgx = ["quartz-common/mock-sgx-cw", "quartz-common/mock-sgx-enclave"] [dependencies] # external +async-trait.workspace = true clap.workspace = true color-eyre.workspace = true ecies.workspace = true @@ -32,19 +33,26 @@ thiserror.workspace = true tokio.workspace = true tonic.workspace = true uuid.workspace = true +futures-util.workspace = true +anyhow.workspace = true +base64 = "0.22.1" +reqwest.workspace = true + # cosmos cosmrs.workspace = true cosmwasm-std.workspace = true tendermint.workspace = true tendermint-light-client.workspace = true +tendermint-rpc.workspace = true # quartz cw-tee-mtcs.workspace = true mtcs.workspace = true # quartz -quartz-common = { workspace = true, features = ["full"] } +quartz-common = { workspace = true, features = ["full"]} +wasmd-client = { workspace = true } [dev-dependencies] cw-multi-test = "2.1.0" diff --git a/apps/mtcs/enclave/src/cli.rs b/apps/mtcs/enclave/src/cli.rs index a062afc..b4979ee 100644 --- a/apps/mtcs/enclave/src/cli.rs +++ b/apps/mtcs/enclave/src/cli.rs @@ -2,7 +2,8 @@ use std::net::SocketAddr; use clap::Parser; use color_eyre::eyre::{eyre, Result}; -use cosmrs::{tendermint::Hash, AccountId}; +use cosmrs::AccountId; +use tendermint::Hash; use tendermint_light_client::types::{Height, TrustThreshold}; fn parse_trust_threshold(s: &str) -> Result { @@ -53,4 +54,10 @@ pub struct Cli { /// Maximum block lag, in seconds #[clap(long, default_value = "5")] pub max_block_lag: u64, + + #[clap(long, default_value = "127.0.0.1:11090")] + pub node_url: String, + + #[clap(long, default_value = "admin")] + pub tx_sender: String, } diff --git a/apps/mtcs/enclave/src/main.rs b/apps/mtcs/enclave/src/main.rs index 7a0e3ef..3775bb1 100644 --- a/apps/mtcs/enclave/src/main.rs +++ b/apps/mtcs/enclave/src/main.rs @@ -1,5 +1,5 @@ -#![doc = include_str!("../README.md")] #![forbid(unsafe_code)] +#![doc = include_str!("../README.md")] #![warn( clippy::checked_conversions, clippy::panic, @@ -16,6 +16,7 @@ mod cli; mod mtcs_server; mod proto; mod types; +mod wslistener; use std::{ sync::{Arc, Mutex}, @@ -25,23 +26,20 @@ use std::{ use clap::Parser; use cli::Cli; use mtcs_server::MtcsService; -use proto::clearing_server::ClearingServer as MtcsServer; use quartz_common::{ contract::state::{Config, LightClientOpts}, enclave::{ attestor::{Attestor, DefaultAttestor}, - server::CoreService, + server::{QuartzServer, WsListenerConfig}, }, - proto::core_server::CoreServer, }; -use tonic::transport::Server; #[tokio::main(flavor = "current_thread")] async fn main() -> Result<(), Box> { let args = Cli::parse(); let light_client_opts = LightClientOpts::new( - args.chain_id, + args.chain_id.clone(), args.trusted_height.into(), Vec::from(args.trusted_hash) .try_into() @@ -64,15 +62,18 @@ async fn main() -> Result<(), Box> { args.tcbinfo_contract.map(|c| c.to_string()), ); + let ws_config = WsListenerConfig { + node_url: args.node_url, + tx_sender: args.tx_sender, + trusted_hash: args.trusted_hash, + trusted_height: args.trusted_height, + chain_id: args.chain_id, + }; + let sk = Arc::new(Mutex::new(None)); - Server::builder() - .add_service(CoreServer::new(CoreService::new( - config.clone(), - sk.clone(), - attestor.clone(), - ))) - .add_service(MtcsServer::new(MtcsService::new(config, sk, attestor))) + QuartzServer::new(config.clone(), sk.clone(), attestor.clone(), ws_config) + .add_service(MtcsService::new(config, sk, attestor)) .serve(args.rpc_addr) .await?; diff --git a/apps/mtcs/enclave/src/mtcs_server.rs b/apps/mtcs/enclave/src/mtcs_server.rs index 9890e4e..954cbe0 100644 --- a/apps/mtcs/enclave/src/mtcs_server.rs +++ b/apps/mtcs/enclave/src/mtcs_server.rs @@ -4,7 +4,6 @@ use std::{ }; use cosmwasm_std::{Addr, HexBinary, Uint128}; -//TODO: get rid of this use cw_tee_mtcs::{ msg::execute::SubmitSetoffsMsg, state::{LiquiditySource, LiquiditySourceType, RawHash, SettleOff, Transfer}, @@ -17,18 +16,29 @@ use mtcs::{ }; use quartz_common::{ contract::{msg::execute::attested::RawAttested, state::Config}, - enclave::{attestor::Attestor, server::ProofOfPublication}, + enclave::{attestor::Attestor, server::IntoServer}, }; use tonic::{Request, Response, Result as TonicResult, Status}; use uuid::Uuid; use crate::{ - proto::{clearing_server::Clearing, RunClearingRequest, RunClearingResponse}, + proto::{ + clearing_server::{Clearing, ClearingServer}, + RunClearingRequest, RunClearingResponse, + }, types::{ContractObligation, RunClearingMessage}, }; pub type RawCipherText = HexBinary; +impl IntoServer for MtcsService { + type Server = ClearingServer>; + + fn into_server(self) -> Self::Server { + ClearingServer::new(self) + } +} + #[derive(Clone, Debug)] pub struct MtcsService { config: Config, // TODO: this config is not used anywhere @@ -59,20 +69,20 @@ where request: Request, ) -> TonicResult> { // Light client check - let message: ProofOfPublication = { + let message: RunClearingMessage = { let message = request.into_inner().message; serde_json::from_str(&message).map_err(|e| Status::invalid_argument(e.to_string()))? }; - let (proof_value, message) = message - .verify(self.config.light_client_opts()) - .map_err(Status::failed_precondition)?; + // let (proof_value, message) = message + // .verify(self.config.light_client_opts()) + // .map_err(Status::failed_precondition)?; - let proof_value_matches_msg = - serde_json::to_string(&message.intents).is_ok_and(|s| s.as_bytes() == proof_value); - if !proof_value_matches_msg { - return Err(Status::failed_precondition("proof verification")); - } + // let proof_value_matches_msg = + // serde_json::to_string(&message.intents).is_ok_and(|s| s.as_bytes() == proof_value); + // if !proof_value_matches_msg { + // return Err(Status::failed_precondition("proof verification")); + // } // TODO: ensure no duplicates somewhere else! let liquidity_sources: Vec = message.liquidity_sources.into_iter().collect(); diff --git a/apps/mtcs/enclave/src/wslistener.rs b/apps/mtcs/enclave/src/wslistener.rs new file mode 100644 index 0000000..1849c7d --- /dev/null +++ b/apps/mtcs/enclave/src/wslistener.rs @@ -0,0 +1,231 @@ +//TODO: get rid of this +use std::{collections::BTreeMap, str::FromStr}; + +use anyhow::{anyhow, Result}; +use base64::prelude::*; +use cosmrs::{tendermint::chain::Id as ChainId, AccountId}; +use cosmwasm_std::{HexBinary, Uint64}; +use cw_tee_mtcs::msg::{ + execute::SubmitSetoffsMsg, AttestedMsg, ExecuteMsg, GetLiquiditySourcesResponse, + QueryMsg::GetLiquiditySources, +}; +use quartz_common::{ + contract::msg::execute::attested::{ + MockAttestation, RawAttested, RawAttestedMsgSansHandler, RawMockAttestation, + }, + enclave::{ + attestor::Attestor, + server::{WebSocketHandler, WsListenerConfig}, + }, +}; +use reqwest::Url; +// use quartz_tee_ra::{intel_sgx::epid::types::ReportBody, IASReport}; +use serde_json::json; +use tendermint_rpc::{event::Event, query::EventType}; +use tonic::Request; +use wasmd_client::{CliWasmdClient, QueryResult, WasmdClient}; + +use crate::{ + mtcs_server::MtcsService, + proto::{clearing_server::Clearing, RunClearingRequest}, + types::RunClearingMessage, +}; + +// TODO: Need to prevent listener from taking actions until handshake is completed +#[async_trait::async_trait] +impl WebSocketHandler for MtcsService { + async fn handle(&self, event: Event, config: WsListenerConfig) -> Result<()> { + // Validation + if !is_init_clearing_event(&event) { + return Ok(()); + } else { + println!("Found clearing event"); + + let mut sender = None; + let mut contract_address = None; + + if let Some(events) = &event.events { + for (key, values) in events { + match key.as_str() { + "message.sender" => { + sender = values.first().cloned(); + } + "wasm._contract_address" => { + contract_address = values.first().cloned(); + } + _ => {} + } + } + } + + // TODO: add some checks based on event messages + + if sender.is_none() || contract_address.is_none() { + return Ok(()); // TODO: change return type + } + + handler( + self, + &contract_address + .expect("infallible") + .parse::() + .map_err(|e| anyhow!(e))?, + sender.expect("infallible"), + &config.node_url, + ) + .await?; + } + + Ok(()) + } +} + +fn is_init_clearing_event(event: &Event) -> bool { + // Check if the event is a transaction type + if let Some(EventType::Tx) = event.event_type() { + // Check for the "wasm.action" key with the value "init_clearing" + if let Some(events) = &event.events { + return events.iter().any(|(key, values)| { + key == "wasm.action" && values.contains(&"init_clearing".to_string()) + }); + } + } + false +} + +async fn handler( + client: &MtcsService, + contract: &AccountId, + sender: String, + node_url: &str, +) -> Result<()> { + let chain_id = &ChainId::from_str("testing")?; + let httpurl = Url::parse(&format!("http://{}", node_url))?; + let wasmd_client = CliWasmdClient::new(httpurl); + + // Query obligations and liquidity sources from chain + let clearing_contents = query_chain(&wasmd_client, contract).await?; + + // Send queried data to enclave over gRPC + let request = Request::new(RunClearingRequest { + message: json!(clearing_contents).to_string(), + }); + + let clearing_response = client + .run(request) + .await + .map_err(|e| anyhow!("Failed to communicate to relayer. {e}"))? + .into_inner(); + // Extract json from the Protobuf message + let attested: RawAttested> = + serde_json::from_str(&clearing_response.message) + .map_err(|e| anyhow!("Error serializing SubmitSetoffs: {}", e))?; + + // TODO add non-mock support, get IAS report and build attested message + let msg = RawAttestedMsgSansHandler(attested.msg); + + let setoffs_msg = ExecuteMsg::SubmitSetoffs::(AttestedMsg { + msg, + attestation: MockAttestation(attested.attestation.try_into().unwrap()).into(), + }); + + // Send setoffs to mtcs contract on chain + let output = + wasmd_client.tx_execute(contract, chain_id, 2000000, &sender, json!(setoffs_msg))?; + + println!("Setoffs TX: {}", output); + Ok(()) +} + +// TODO: replace raw queries with smart +async fn query_chain( + wasmd_client: &CliWasmdClient, + contract: &AccountId, +) -> Result { + // Get epoch counter + let resp: QueryResult = wasmd_client + .query_raw(contract, hex::encode("epoch_counter")) + .map_err(|e| anyhow!("Problem querying epoch: {}", e))?; + + let mut epoch_counter: usize = String::from_utf8(BASE64_STANDARD.decode(resp.data)?)? + .trim_matches('"') + .parse::()?; + + if epoch_counter > 1 { + epoch_counter -= 1; + } + + // TODO: replace with tracer log here + // println!("epoch: {}", epoch_counter); + + // Get obligations + let resp: QueryResult = wasmd_client + .query_raw( + contract, + hex::encode(format!("{}/obligations", epoch_counter)), + ) + .map_err(|e| anyhow!("Problem querying obligatons: {}", e))?; + + let decoded_obligs = BASE64_STANDARD.decode(resp.data)?; + let obligations_map: BTreeMap = + serde_json::from_slice(&decoded_obligs).unwrap_or_default(); + // println!("obligations \n {:?}", obligations_map); + // TODO: replace with tracer log here + + // Get liquidity sources + let resp: QueryResult = wasmd_client + .query_smart( + contract, + json!(GetLiquiditySources { + epoch: Some(Uint64::new(epoch_counter as u64)) + }), + ) + .map_err(|e| anyhow!("Problem querying liquidity sources: {}", e))?; + + let liquidity_sources = resp.data.liquidity_sources; + // TODO: replace with tracer log here + // println!("liquidity_sources \n {:?}", liquidity_sources); + + Ok(RunClearingMessage { + intents: obligations_map, + liquidity_sources: liquidity_sources.into_iter().collect(), + }) +} + +// Request the IAS report for EPID attestations +// async fn gramine_ias_request( +// attested_msg: Vec, +// user: &str, +// ) -> Result { +// let ias_api_key = String::from("669244b3e6364b5888289a11d2a1726d"); +// let ra_client_spid = String::from("51CAF5A48B450D624AEFE3286D314894"); +// let quote_file = format!("/tmp/{}_test.quote", user); +// let report_file = format!("/tmp/{}_datareport", user); +// let report_sig_file = format!("/tmp/{}_datareportsig", user); + +// // Write the binary data to a file +// let mut file = File::create("e_file).await?; +// file.write_all(&attested_msg) +// .await +// .map_err(|e| anyhow!("Couldn't write to file. {e}"))?; + +// let mut gramine = Command::new("gramine-sgx-ias-request"); +// let command = gramine +// .arg("report") +// .args(["-g", &ra_client_spid]) +// .args(["-k", &ias_api_key]) +// .args(["-q", "e_file]) +// .args(["-r", &report_file]) +// .args(["-s", &report_sig_file]); + +// let output = command.output()?; +// if !output.status.success() { +// return Err(anyhow!("Couldn't run gramine. {:?}", output)); +// } + +// let report: ReportBody = serde_json::from_str(&fs::read_to_string(report_file).await?)?; +// let report_sig_str = fs::read_to_string(report_sig_file).await?.replace('\r', ""); +// let report_sig: Binary = Binary::from_base64(report_sig_str.trim())?; + +// Ok(EpidAttestation::new(IASReport { report, report_sig })) +// } diff --git a/apps/transfers/contracts/Cargo.toml b/apps/transfers/contracts/Cargo.toml index a8a9903..56d6523 100644 --- a/apps/transfers/contracts/Cargo.toml +++ b/apps/transfers/contracts/Cargo.toml @@ -44,7 +44,8 @@ cw20-base = { version = "2.0.0", default-features = false, features = ["library" cw-utils = { version = "2.0.0", default-features = false } # quartz -quartz-common = { path = "../../../core/quartz-common", features = ["contract"] } +# quartz-common = { git = "ssh://git@github.com/informalsystems/cycles-quartz.git", features=["contract"]} +quartz-common = { path = "../../../core/quartz-common", features=["contract"]} # patch indirect deps getrandom = { version = "0.2.15", default-features = false, features = ["js"] } diff --git a/apps/transfers/contracts/src/contract.rs b/apps/transfers/contracts/src/contract.rs index a547ab8..b39bac2 100644 --- a/apps/transfers/contracts/src/contract.rs +++ b/apps/transfers/contracts/src/contract.rs @@ -230,6 +230,8 @@ pub mod execute { pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { QueryMsg::GetBalance { address } => to_json_binary(&query::get_balance(deps, address)?), + QueryMsg::GetRequests {} => to_json_binary(&query::get_requests(deps)?), + QueryMsg::GetState {} => to_json_binary(&query::get_state(deps)?), } } mod query { @@ -239,4 +241,12 @@ mod query { let balance = BALANCES.may_load(deps.storage, &address)?; Ok(balance.unwrap_or_default()) } + + pub fn get_requests(deps: Deps) -> StdResult> { + Ok(REQUESTS.load(deps.storage)?) + } + + pub fn get_state(deps: Deps) -> StdResult { + Ok(STATE.load(deps.storage)?) + } } diff --git a/apps/transfers/contracts/src/msg.rs b/apps/transfers/contracts/src/msg.rs index adb0cec..4db7d86 100644 --- a/apps/transfers/contracts/src/msg.rs +++ b/apps/transfers/contracts/src/msg.rs @@ -4,7 +4,7 @@ use quartz_common::contract::{ prelude::*, }; -type AttestedMsg = RawAttested, RA>; +pub type AttestedMsg = RawAttested, RA>; #[cw_serde] pub struct InstantiateMsg { @@ -15,6 +15,8 @@ pub struct InstantiateMsg { #[cw_serde] pub enum QueryMsg { GetBalance { address: String }, + GetRequests {}, + GetState {}, } #[cw_serde] diff --git a/apps/transfers/enclave/Cargo.lock b/apps/transfers/enclave/Cargo.lock index 887cadf..6bb05cb 100644 --- a/apps/transfers/enclave/Cargo.lock +++ b/apps/transfers/enclave/Cargo.lock @@ -90,6 +90,9 @@ name = "anyhow" version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +dependencies = [ + "backtrace", +] [[package]] name = "ark-bls12-381" @@ -296,6 +299,23 @@ dependencies = [ "syn 2.0.76", ] +[[package]] +name = "async-tungstenite" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3609af4bbf701ddaf1f6bb4e6257dff4ff8932327d0e685d3f653724c258b1ac" +dependencies = [ + "futures-io", + "futures-util", + "log", + "pin-project-lite", + "rustls-native-certs 0.7.3", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.25.0", + "tungstenite", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -330,7 +350,7 @@ dependencies = [ "rustversion", "serde", "sync_wrapper 1.0.1", - "tower", + "tower 0.4.13", "tower-layer", "tower-service", ] @@ -1290,6 +1310,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c606d892c9de11507fa0dcffc116434f94e105d0bbdc4e405b61519464c49d7b" dependencies = [ + "eyre", "paste", ] @@ -1299,6 +1320,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -1344,6 +1380,17 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.76", +] + [[package]] name = "futures-sink" version = "0.3.30" @@ -1363,10 +1410,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-core", + "futures-macro", "futures-sink", "futures-task", "pin-project-lite", "pin-utils", + "slab", ] [[package]] @@ -1656,9 +1705,27 @@ dependencies = [ "futures-util", "http 0.2.12", "hyper 0.14.30", - "rustls", + "rustls 0.21.12", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.4.1", + "hyper-util", + "rustls 0.23.12", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.0", + "tower-service", + "webpki-roots", ] [[package]] @@ -1674,6 +1741,22 @@ dependencies = [ "tower-service", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.4.1", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.7" @@ -1689,7 +1772,7 @@ dependencies = [ "pin-project-lite", "socket2", "tokio", - "tower", + "tower 0.4.13", "tower-service", "tracing", ] @@ -1910,6 +1993,15 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + [[package]] name = "matchit" version = "0.7.3" @@ -2060,6 +2152,23 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" +[[package]] +name = "native-tls" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nom" version = "7.1.3" @@ -2179,12 +2288,50 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "openssl" +version = "0.10.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.76", +] + [[package]] name = "openssl-probe" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "openssl-sys" +version = "0.9.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "owo-colors" version = "3.5.0" @@ -2336,6 +2483,12 @@ dependencies = [ "spki", ] +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + [[package]] name = "polyval" version = "0.6.2" @@ -2481,26 +2634,34 @@ name = "quartz-app-transfers-enclave" version = "0.1.0" dependencies = [ "anyhow", + "async-trait", + "base64 0.22.1", "clap", "color-eyre", "cosmrs", "cosmwasm-std", "cw-multi-test", "ecies", + "futures-util", "hex", "k256", "prost 0.13.1", "quartz-common", + "reqwest 0.12.7", "serde", "serde_json", "sha2 0.10.8", "tendermint 0.38.1", "tendermint-light-client", + "tendermint-rpc", "thiserror", + "tm-prover", "tokio", "tonic", "tonic-build", + "tracing", "transfers-contract", + "wasmd-client", ] [[package]] @@ -2534,12 +2695,15 @@ dependencies = [ name = "quartz-enclave" version = "0.1.0" dependencies = [ + "anyhow", + "async-trait", "clap", "color-eyre", "cosmrs", "cosmwasm-std", "cw-proof", "ecies", + "futures-util", "hex", "k256", "mtcs", @@ -2552,9 +2716,12 @@ dependencies = [ "sha2 0.10.8", "tendermint 0.38.1", "tendermint-light-client", + "tendermint-rpc", + "thiserror", "tm-stateless-verifier", "tokio", "tonic", + "tower 0.5.1", ] [[package]] @@ -2589,6 +2756,54 @@ dependencies = [ "zeroize", ] +[[package]] +name = "quinn" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" +dependencies = [ + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls 0.23.12", + "socket2", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" +dependencies = [ + "bytes", + "rand", + "ring", + "rustc-hash", + "rustls 0.23.12", + "slab", + "thiserror", + "tinyvec", + "tracing", +] + +[[package]] +name = "quinn-udp" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fe68c2e9e1a1234e218683dbdf9f9dfcb094113c5ac2b938dfcb9bab4c4140b" +dependencies = [ + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.59.0", +] + [[package]] name = "quote" version = "1.0.37" @@ -2665,8 +2880,17 @@ checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", ] [[package]] @@ -2677,9 +2901,15 @@ checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.4", ] +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.8.4" @@ -2701,7 +2931,7 @@ dependencies = [ "http 0.2.12", "http-body 0.4.6", "hyper 0.14.30", - "hyper-rustls", + "hyper-rustls 0.24.2", "ipnet", "js-sys", "log", @@ -2709,16 +2939,16 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls", - "rustls-native-certs", - "rustls-pemfile", + "rustls 0.21.12", + "rustls-native-certs 0.6.3", + "rustls-pemfile 1.0.4", "serde", "serde_json", "serde_urlencoded", "sync_wrapper 0.1.2", - "system-configuration", + "system-configuration 0.5.1", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", "tower-service", "url", "wasm-bindgen", @@ -2727,6 +2957,54 @@ dependencies = [ "winreg", ] +[[package]] +name = "reqwest" +version = "0.12.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.4.6", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.4.1", + "hyper-rustls 0.27.3", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls 0.23.12", + "rustls-pemfile 2.1.3", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.1", + "system-configuration 0.6.1", + "tokio", + "tokio-native-tls", + "tokio-rustls 0.26.0", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", + "windows-registry", +] + [[package]] name = "rfc6979" version = "0.4.0" @@ -2767,6 +3045,12 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" + [[package]] name = "rustc_version" version = "0.4.1" @@ -2806,10 +3090,38 @@ checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", "ring", - "rustls-webpki", + "rustls-webpki 0.101.7", "sct", ] +[[package]] +name = "rustls" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki 0.102.7", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls" +version = "0.23.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki 0.102.7", + "subtle", + "zeroize", +] + [[package]] name = "rustls-native-certs" version = "0.6.3" @@ -2817,7 +3129,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" dependencies = [ "openssl-probe", - "rustls-pemfile", + "rustls-pemfile 1.0.4", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-native-certs" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" +dependencies = [ + "openssl-probe", + "rustls-pemfile 2.1.3", + "rustls-pki-types", "schannel", "security-framework", ] @@ -2831,6 +3156,22 @@ dependencies = [ "base64 0.21.7", ] +[[package]] +name = "rustls-pemfile" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +dependencies = [ + "base64 0.22.1", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" + [[package]] name = "rustls-webpki" version = "0.101.7" @@ -2841,6 +3182,17 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustls-webpki" +version = "0.102.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84678086bd54edf2b415183ed7a94d0efb049f1b646a33e22a36f3794be6ae56" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.17" @@ -3085,6 +3437,17 @@ dependencies = [ "syn 2.0.76", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + [[package]] name = "sha2" version = "0.9.9" @@ -3119,6 +3482,15 @@ dependencies = [ "keccak", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" @@ -3242,6 +3614,9 @@ name = "sync_wrapper" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] [[package]] name = "synstructure" @@ -3262,7 +3637,18 @@ checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ "bitflags 1.3.2", "core-foundation", - "system-configuration-sys", + "system-configuration-sys 0.5.0", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "system-configuration-sys 0.6.0", ] [[package]] @@ -3275,6 +3661,16 @@ dependencies = [ "libc", ] +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tcbinfo" version = "0.1.0" @@ -3284,6 +3680,7 @@ dependencies = [ "cw-storage-plus", "cw2", "der", + "getrandom", "hex", "mc-attestation-verifier", "p256", @@ -3403,6 +3800,30 @@ dependencies = [ "tendermint-light-client-verifier", "tendermint-rpc", "time", + "tokio", + "tracing", +] + +[[package]] +name = "tendermint-light-client-detector" +version = "0.38.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1ac1607eb7a3393313558b339c36eebeba15aa7f2d101d1d47299e65825152" +dependencies = [ + "crossbeam-channel", + "derive_more 0.99.18", + "flex-error", + "futures", + "serde", + "serde_cbor", + "serde_derive", + "serde_json", + "static_assertions", + "tendermint 0.38.1", + "tendermint-light-client", + "tendermint-proto 0.38.1", + "tendermint-rpc", + "time", "tracing", ] @@ -3458,6 +3879,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02f96a2b8a0d3d0b59e4024b1a6bdc1589efc6af4709d08a480a20cc4ba90f63" dependencies = [ "async-trait", + "async-tungstenite", "bytes", "flex-error", "futures", @@ -3465,7 +3887,7 @@ dependencies = [ "peg", "pin-project", "rand", - "reqwest", + "reqwest 0.11.27", "semver", "serde", "serde_bytes", @@ -3504,6 +3926,16 @@ dependencies = [ "syn 2.0.76", ] +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "time" version = "0.3.36" @@ -3550,6 +3982,26 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "tm-prover" +version = "0.1.0" +dependencies = [ + "clap", + "color-eyre", + "cosmrs", + "cw-proof", + "futures", + "serde", + "serde_json", + "tendermint 0.38.1", + "tendermint-light-client", + "tendermint-light-client-detector", + "tendermint-rpc", + "tokio", + "tracing", + "tracing-subscriber", +] + [[package]] name = "tm-stateless-verifier" version = "0.1.0" @@ -3586,13 +4038,45 @@ dependencies = [ "syn 2.0.76", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls", + "rustls 0.21.12", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls 0.22.4", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls 0.23.12", + "rustls-pki-types", "tokio", ] @@ -3678,7 +4162,7 @@ dependencies = [ "socket2", "tokio", "tokio-stream", - "tower", + "tower 0.4.13", "tower-layer", "tower-service", "tracing", @@ -3717,6 +4201,16 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f" +dependencies = [ + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.3" @@ -3760,6 +4254,21 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "once_cell", + "regex", + "sharded-slab", + "thread_local", + "tracing", + "tracing-core", +] + [[package]] name = "transfers-contract" version = "0.1.0" @@ -3784,6 +4293,27 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.1.0", + "httparse", + "log", + "rand", + "rustls 0.22.4", + "rustls-pki-types", + "sha1", + "thiserror", + "url", + "utf-8", +] + [[package]] name = "typenum" version = "1.17.0" @@ -3844,12 +4374,24 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "uuid" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" @@ -3948,6 +4490,18 @@ version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +[[package]] +name = "wasmd-client" +version = "0.1.0" +dependencies = [ + "anyhow", + "cosmrs", + "hex", + "reqwest 0.12.7", + "serde", + "serde_json", +] + [[package]] name = "web-sys" version = "0.3.70" @@ -3958,6 +4512,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-roots" +version = "0.26.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bd24728e5af82c6c4ec1b66ac4844bdf8156257fccda846ec58b42cd0cdbe6a" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "winapi-util" version = "0.1.9" @@ -3967,6 +4530,36 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/apps/transfers/enclave/Cargo.toml b/apps/transfers/enclave/Cargo.toml index ae16946..34eba45 100644 --- a/apps/transfers/enclave/Cargo.toml +++ b/apps/transfers/enclave/Cargo.toml @@ -15,7 +15,9 @@ default = [] [dependencies] # external -anyhow = { version = "1.0.86", default-features = false } +async-trait = "0.1.81" +anyhow = { version = "1.0.86" } +base64 = "0.22.1" clap = { version = "4.1.8", default-features = false, features = ["derive", "std"] } color-eyre = { version = "0.6.2", default-features = false } ecies = { version = "0.2.3", default-features = false, features = ["pure"] } @@ -25,19 +27,26 @@ prost = { version = "=0.13.1", default-features = false } serde = { version = "1.0.203", default-features = false, features = ["derive"] } serde_json = { version = "1.0.94", default-features = false, features = ["alloc"] } sha2 = { version = "0.10.8", default-features = false } +reqwest = "0.12.7" thiserror = { version = "1.0.49", default-features = false } tokio = { version = "=1.39.2", default-features = false, features = ["macros", "rt"] } tonic = { version = "=0.12.1", default-features = false, features = ["codegen", "prost", "transport"] } +tracing = "0.1.39" +futures-util = "0.3.30" # cosmos cosmrs = { version = "=0.17.0", default-features = false } cosmwasm-std = { version = "2.1.1", default-features = false, features = ["std"] } tendermint = { version = "=0.38.1", default-features = false } +tendermint-rpc = { version = "=0.38.1", default-features = false } tendermint-light-client = { version = "=0.38.1", default-features = false, features = ["rust-crypto"] } transfers-contract = { path = "../contracts", default-features = false } # quartz -quartz-common = { path = "../../../core/quartz-common", features = ["full"] } +# quartz-common = { git = "ssh://git@github.com/informalsystems/cycles-quartz.git", features=["full"]} +quartz-common = { path = "../../../core/quartz-common", features=["full"]} +wasmd-client = { path = "../../../cosmwasm/packages/wasmd-client"} +tm-prover = { path = "../../../utils/tm-prover", default-features = false } [dev-dependencies] cw-multi-test = "2.1.0" diff --git a/apps/transfers/enclave/src/cli.rs b/apps/transfers/enclave/src/cli.rs index 46d82e2..2464fcf 100644 --- a/apps/transfers/enclave/src/cli.rs +++ b/apps/transfers/enclave/src/cli.rs @@ -2,7 +2,8 @@ use std::{env, net::SocketAddr}; use clap::Parser; use color_eyre::eyre::{eyre, Result}; -use cosmrs::{tendermint::Hash, AccountId}; +use cosmrs::AccountId; +use tendermint::Hash; use tendermint_light_client::types::{Height, TrustThreshold}; fn parse_trust_threshold(s: &str) -> Result { @@ -53,10 +54,16 @@ pub struct Cli { /// Maximum block lag, in seconds #[clap(long, default_value = "5")] pub max_block_lag: u64, + + #[clap(long, default_value = "127.0.0.1:11090")] + pub node_url: String, + + #[clap(long, default_value = "admin")] + pub tx_sender: String, } fn default_rpc_addr() -> SocketAddr { - let port = env::var("QUARTZ_PORT").unwrap_or_else(|_| "11090".to_string()); + let port = env::var("QUARTZ_ENCLAVE_RPC_PORT").unwrap_or_else(|_| "11090".to_string()); format!("127.0.0.1:{}", port) .parse() .expect("Invalid socket address") diff --git a/apps/transfers/enclave/src/main.rs b/apps/transfers/enclave/src/main.rs index b16c7e9..c11a7dc 100644 --- a/apps/transfers/enclave/src/main.rs +++ b/apps/transfers/enclave/src/main.rs @@ -16,6 +16,7 @@ pub mod cli; pub mod proto; pub mod state; pub mod transfers_server; +pub mod wslistener; use std::{ sync::{Arc, Mutex}, @@ -24,16 +25,13 @@ use std::{ use clap::Parser; use cli::Cli; -use proto::settlement_server::SettlementServer as TransfersServer; use quartz_common::{ contract::state::{Config, LightClientOpts}, enclave::{ attestor::{Attestor, DefaultAttestor}, - server::CoreService, + server::{QuartzServer, WsListenerConfig}, }, - proto::core_server::CoreServer, }; -use tonic::transport::Server; use transfers_server::TransfersService; #[tokio::main(flavor = "current_thread")] @@ -41,7 +39,7 @@ async fn main() -> Result<(), Box> { let args = Cli::parse(); let light_client_opts = LightClientOpts::new( - args.chain_id, + args.chain_id.clone(), args.trusted_height.into(), Vec::from(args.trusted_hash) .try_into() @@ -64,17 +62,18 @@ async fn main() -> Result<(), Box> { args.tcbinfo_contract.map(|c| c.to_string()), ); + let ws_config = WsListenerConfig { + node_url: args.node_url, + tx_sender: args.tx_sender, + trusted_hash: args.trusted_hash, + trusted_height: args.trusted_height, + chain_id: args.chain_id, + }; + let sk = Arc::new(Mutex::new(None)); - Server::builder() - .add_service(CoreServer::new(CoreService::new( - config.clone(), - sk.clone(), - attestor.clone(), - ))) - .add_service(TransfersServer::new(TransfersService::new( - config, sk, attestor, - ))) + QuartzServer::new(config.clone(), sk.clone(), attestor.clone(), ws_config) + .add_service(TransfersService::new(config, sk, attestor)) .serve(args.rpc_addr) .await?; diff --git a/apps/transfers/enclave/src/prost/transfers.rs b/apps/transfers/enclave/src/prost/transfers.rs index 5f31588..bf1d818 100644 --- a/apps/transfers/enclave/src/prost/transfers.rs +++ b/apps/transfers/enclave/src/prost/transfers.rs @@ -23,6 +23,12 @@ pub struct QueryResponse { #[prost(string, tag = "1")] pub message: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] +pub struct ListenRequest {} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, Copy, PartialEq, ::prost::Message)] +pub struct ListenResponse {} /// Generated client implementations. pub mod settlement_client { #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] @@ -151,6 +157,115 @@ pub mod settlement_client { } } } +/// Generated client implementations. +pub mod event_listener_client { + #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] + use tonic::codegen::*; + use tonic::codegen::http::Uri; + #[derive(Debug, Clone)] + pub struct EventListenerClient { + inner: tonic::client::Grpc, + } + impl EventListenerClient { + /// Attempt to create a new client by connecting to a given endpoint. + pub async fn connect(dst: D) -> Result + where + D: TryInto, + D::Error: Into, + { + let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; + Ok(Self::new(conn)) + } + } + impl EventListenerClient + where + T: tonic::client::GrpcService, + T::Error: Into, + T::ResponseBody: Body + Send + 'static, + ::Error: Into + Send, + { + pub fn new(inner: T) -> Self { + let inner = tonic::client::Grpc::new(inner); + Self { inner } + } + pub fn with_origin(inner: T, origin: Uri) -> Self { + let inner = tonic::client::Grpc::with_origin(inner, origin); + Self { inner } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> EventListenerClient> + where + F: tonic::service::Interceptor, + T::ResponseBody: Default, + T: tonic::codegen::Service< + http::Request, + Response = http::Response< + >::ResponseBody, + >, + >, + , + >>::Error: Into + Send + Sync, + { + EventListenerClient::new(InterceptedService::new(inner, interceptor)) + } + /// Compress requests with the given encoding. + /// + /// This requires the server to support it otherwise it might respond with an + /// error. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.send_compressed(encoding); + self + } + /// Enable decompressing responses. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.accept_compressed(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_decoding_message_size(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_encoding_message_size(limit); + self + } + pub async fn listen( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result, tonic::Status> { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/transfers.EventListener/Listen", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("transfers.EventListener", "Listen")); + self.inner.unary(req, path, codec).await + } + } +} /// Generated server implementations. pub mod settlement_server { #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] @@ -363,3 +478,170 @@ pub mod settlement_server { const NAME: &'static str = "transfers.Settlement"; } } +/// Generated server implementations. +pub mod event_listener_server { + #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] + use tonic::codegen::*; + /// Generated trait containing gRPC methods that should be implemented for use with EventListenerServer. + #[async_trait] + pub trait EventListener: Send + Sync + 'static { + async fn listen( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + } + #[derive(Debug)] + pub struct EventListenerServer { + inner: Arc, + accept_compression_encodings: EnabledCompressionEncodings, + send_compression_encodings: EnabledCompressionEncodings, + max_decoding_message_size: Option, + max_encoding_message_size: Option, + } + impl EventListenerServer { + pub fn new(inner: T) -> Self { + Self::from_arc(Arc::new(inner)) + } + pub fn from_arc(inner: Arc) -> Self { + Self { + inner, + accept_compression_encodings: Default::default(), + send_compression_encodings: Default::default(), + max_decoding_message_size: None, + max_encoding_message_size: None, + } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> InterceptedService + where + F: tonic::service::Interceptor, + { + InterceptedService::new(Self::new(inner), interceptor) + } + /// Enable decompressing requests with the given encoding. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.accept_compression_encodings.enable(encoding); + self + } + /// Compress responses with the given encoding, if the client supports it. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.send_compression_encodings.enable(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.max_decoding_message_size = Some(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.max_encoding_message_size = Some(limit); + self + } + } + impl tonic::codegen::Service> for EventListenerServer + where + T: EventListener, + B: Body + Send + 'static, + B::Error: Into + Send + 'static, + { + type Response = http::Response; + type Error = std::convert::Infallible; + type Future = BoxFuture; + fn poll_ready( + &mut self, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + fn call(&mut self, req: http::Request) -> Self::Future { + match req.uri().path() { + "/transfers.EventListener/Listen" => { + #[allow(non_camel_case_types)] + struct ListenSvc(pub Arc); + impl< + T: EventListener, + > tonic::server::UnaryService + for ListenSvc { + type Response = super::ListenResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::listen(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = ListenSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + _ => { + Box::pin(async move { + Ok( + http::Response::builder() + .status(200) + .header("grpc-status", tonic::Code::Unimplemented as i32) + .header( + http::header::CONTENT_TYPE, + tonic::metadata::GRPC_CONTENT_TYPE, + ) + .body(empty_body()) + .unwrap(), + ) + }) + } + } + } + } + impl Clone for EventListenerServer { + fn clone(&self) -> Self { + let inner = self.inner.clone(); + Self { + inner, + accept_compression_encodings: self.accept_compression_encodings, + send_compression_encodings: self.send_compression_encodings, + max_decoding_message_size: self.max_decoding_message_size, + max_encoding_message_size: self.max_encoding_message_size, + } + } + } + impl tonic::server::NamedService for EventListenerServer { + const NAME: &'static str = "transfers.EventListener"; + } +} diff --git a/apps/transfers/enclave/src/transfers_server.rs b/apps/transfers/enclave/src/transfers_server.rs index b4aef9e..848e4b8 100644 --- a/apps/transfers/enclave/src/transfers_server.rs +++ b/apps/transfers/enclave/src/transfers_server.rs @@ -11,7 +11,10 @@ use quartz_common::{ msg::execute::attested::{HasUserData, RawAttested}, state::{Config, UserData}, }, - enclave::{attestor::Attestor, server::ProofOfPublication}, + enclave::{ + attestor::Attestor, + server::{IntoServer, ProofOfPublication}, + }, }; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; @@ -20,11 +23,20 @@ use transfers_contract::msg::execute::{ClearTextTransferRequestMsg, Request as T use crate::{ proto::{ - settlement_server::Settlement, QueryRequest, QueryResponse, UpdateRequest, UpdateResponse, + settlement_server::{Settlement, SettlementServer}, + QueryRequest, QueryResponse, UpdateRequest, UpdateResponse, }, state::{RawBalance, RawState, State}, }; +impl IntoServer for TransfersService { + type Server = SettlementServer>; + + fn into_server(self) -> Self::Server { + SettlementServer::new(self) + } +} + pub type RawCipherText = HexBinary; #[derive(Clone, Debug)] @@ -34,17 +46,17 @@ pub struct TransfersService { attestor: A, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, Default)] pub struct UpdateRequestMessage { - state: HexBinary, - requests: Vec, + pub state: HexBinary, + pub requests: Vec, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct QueryRequestMessage { - state: HexBinary, - address: Addr, - ephemeral_pubkey: HexBinary, + pub state: HexBinary, + pub address: Addr, + pub ephemeral_pubkey: HexBinary, } #[derive(Clone, Debug, Serialize, Deserialize)] diff --git a/apps/transfers/enclave/src/wslistener.rs b/apps/transfers/enclave/src/wslistener.rs new file mode 100644 index 0000000..f4246ef --- /dev/null +++ b/apps/transfers/enclave/src/wslistener.rs @@ -0,0 +1,321 @@ +//TODO: get rid of this +use std::str::FromStr; + +use anyhow::{anyhow, Result}; +use cosmrs::{tendermint::chain::Id as ChainId, AccountId}; +use cosmwasm_std::{Addr, HexBinary}; +use futures_util::StreamExt; +use quartz_common::{ + contract::msg::execute::attested::{ + MockAttestation, RawAttested, RawAttestedMsgSansHandler, RawMockAttestation, + }, + enclave::{ + attestor::Attestor, + server::{WebSocketHandler, WsListenerConfig}, + }, +}; +use reqwest::Url; +use serde_json::json; +use tendermint_rpc::{event::Event, query::EventType, SubscriptionClient, WebSocketClient}; +use tm_prover::{config::Config as TmProverConfig, prover::prove}; +use tonic::Request; +use tracing::info; +use transfers_contract::msg::{ + execute::{QueryResponseMsg, Request as TransferRequest, UpdateMsg}, + AttestedMsg, ExecuteMsg, + QueryMsg::{GetRequests, GetState}, +}; +use wasmd_client::{CliWasmdClient, QueryResult, WasmdClient}; + +use crate::{ + proto::{settlement_server::Settlement, QueryRequest, UpdateRequest}, + transfers_server::{QueryRequestMessage, TransfersService, UpdateRequestMessage}, +}; + +// TODO: Need to prevent listener from taking actions until handshake is completed +#[async_trait::async_trait] +impl WebSocketHandler for TransfersService { + async fn handle(&self, event: Event, config: WsListenerConfig) -> Result<()> { + // Validation + let is_transfer = is_transfer_event(&event); + let is_query = is_query_event(&event); + + if !is_transfer && !is_query { + return Ok(()); + } else { + let mut sender = None; + let mut contract_address = None; + let mut emphemeral_pubkey = None; + + if let Some(events) = &event.events { + for (key, values) in events { + match key.as_str() { + "message.sender" => { + sender = values.first().cloned(); + } + "execute._contract_address" => { + contract_address = values.first().cloned(); + } + "wasm-query_balance.emphemeral_pubkey" => { + // TODO: fix typo + emphemeral_pubkey = values.first().cloned(); + } + _ => {} + } + } + } + + if contract_address.is_none() { + return Ok(()); + } + + if is_transfer { + println!("Processing transfer event"); + transfer_handler( + self, + &contract_address + .expect("must be included in transfers event") + .parse::() + .map_err(|e| anyhow!(e))?, + &config, + ) + .await?; + } else if is_query { + println!("Processing query event"); + query_handler( + self, + &contract_address + .expect("must be included in query event") + .parse::() + .map_err(|e| anyhow!(e))?, + sender.expect("must be included in query event"), + emphemeral_pubkey.expect("must be included in query event"), + &config, + ) + .await?; + } + } + + Ok(()) + } +} + +fn is_transfer_event(event: &Event) -> bool { + // Check if the event is a transaction type + if let Some(EventType::Tx) = event.event_type() { + // Check for the "wasm.action" key with the value "init_clearing" + if let Some(events) = &event.events { + return events.iter().any(|(key, _)| key == "wasm-transfer.action"); + } + } + false +} + +fn is_query_event(event: &Event) -> bool { + // Check if the event is a transaction type + if let Some(EventType::Tx) = event.event_type() { + // Check for the "wasm.action" key with the value "init_clearing" + if let Some(events) = &event.events { + return events + .iter() + .any(|(key, _)| key.starts_with("wasm-query_balance")); + } + } + false +} + +async fn transfer_handler( + client: &TransfersService, + contract: &AccountId, + ws_config: &WsListenerConfig, +) -> Result<()> { + let chain_id = &ChainId::from_str(&ws_config.chain_id)?; + let httpurl = Url::parse(&format!("http://{}", ws_config.node_url))?; + let wasmd_client = CliWasmdClient::new(httpurl.clone()); + + // Query chain + // Get epoch, obligations, liquidity sources + let resp: QueryResult> = wasmd_client + .query_smart(contract, json!(GetRequests {})) + .map_err(|e| anyhow!("Problem querying epoch: {}", e))?; + let requests = resp.data; + + let resp: QueryResult = wasmd_client + .query_smart(contract, json!(GetState {})) + .map_err(|e| anyhow!("Problem querying epoch: {}", e))?; + let state = resp.data; + + // Request body contents + let update_contents = UpdateRequestMessage { state, requests }; + + // Wait 2 blocks + info!("Waiting 2 blocks for light client proof"); + let wsurl = format!("ws://{}/websocket", ws_config.node_url); + two_block_waitoor(&wsurl).await?; + + // Call tm prover with trusted hash and height + let prover_config = TmProverConfig { + primary: httpurl.as_str().parse()?, + witnesses: httpurl.as_str().parse()?, + trusted_height: ws_config.trusted_height, + trusted_hash: ws_config.trusted_hash, + verbose: "1".parse()?, // TODO: both tm-prover and cli define the same Verbosity struct. Need to define this once and import + contract_address: contract.clone(), + storage_key: "requests".to_string(), + chain_id: ws_config.chain_id.to_string(), + ..Default::default() + }; + + let proof_output = tokio::task::spawn_blocking(move || { + // Create a new runtime inside the blocking thread. + let rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(async { + prove(prover_config) + .await + .map_err(|report| anyhow!("Tendermint prover failed. Report: {}", report)) + }) + }) + .await??; // Handle both JoinError and your custom error + + // Merge the UpdateRequestMessage with the proof + let mut proof_json = serde_json::to_value(proof_output)?; + proof_json["msg"] = serde_json::to_value(&update_contents)?; + + // Build final request object + let request = Request::new(UpdateRequest { + message: json!(proof_json).to_string(), + }); + + // Send UpdateRequestMessage request to enclave over tonic gRPC client + let update_response = client + .run(request) + .await + .map_err(|e| anyhow!("Failed to communicate to relayer. {e}"))? + .into_inner(); + + // Extract json from enclave response + let attested: RawAttested = + serde_json::from_str(&update_response.message) + .map_err(|e| anyhow!("Error deserializing UpdateMsg from enclave: {}", e))?; + + // Build on-chain response + // TODO add non-mock support + let setoffs_msg = ExecuteMsg::Update::(AttestedMsg { + msg: RawAttestedMsgSansHandler(attested.msg), + attestation: MockAttestation( + attested + .attestation + .as_slice() + .try_into() + .map_err(|_| anyhow!("slice with incorrect length"))?, + ) + .into(), + }); + + // Post response to chain + let output = wasmd_client.tx_execute( + contract, + chain_id, + 2000000, + &ws_config.tx_sender, + json!(setoffs_msg), + )?; + + println!("Output TX: {}", output); + Ok(()) +} + +async fn query_handler( + client: &TransfersService, + contract: &AccountId, + msg_sender: String, + pubkey: String, + ws_config: &WsListenerConfig, +) -> Result<()> { + let chain_id = &ChainId::from_str(&ws_config.chain_id)?; + let httpurl = Url::parse(&format!("http://{}", ws_config.node_url))?; + let wasmd_client = CliWasmdClient::new(httpurl); + + // Query Chain + // Get state + let resp: QueryResult = wasmd_client + .query_smart(contract, json!(GetState {})) + .map_err(|e| anyhow!("Problem querying epoch: {}", e))?; + let state = resp.data; + + // Build request + let update_contents = QueryRequestMessage { + state, + address: Addr::unchecked(&msg_sender), // sender comes from TX event, therefore is checked + ephemeral_pubkey: HexBinary::from_hex(&pubkey)?, + }; + + // Send QueryRequestMessage to enclave over tonic gRPC client + let request = Request::new(QueryRequest { + message: json!(update_contents).to_string(), + }); + + let query_response = client + .query(request) + .await + .map_err(|e| anyhow!("Failed to communicate to relayer. {e}"))? + .into_inner(); + + // Extract json from the enclave response + let attested: RawAttested = + serde_json::from_str(&query_response.message) + .map_err(|e| anyhow!("Error deserializing QueryResponseMsg from enclave: {}", e))?; + + // Build on-chain response + // TODO add non-mock support + let setoffs_msg = ExecuteMsg::QueryResponse::(AttestedMsg { + msg: RawAttestedMsgSansHandler(attested.msg), + attestation: MockAttestation( + attested + .attestation + .as_slice() + .try_into() + .map_err(|_| anyhow!("slice with incorrect length"))?, + ) + .into(), + }); + + // Post response to chain + let output = wasmd_client.tx_execute( + contract, + chain_id, + 2000000, + &ws_config.tx_sender, + json!(setoffs_msg), + )?; + + println!("Output TX: {}", output); + Ok(()) +} + +async fn two_block_waitoor(wsurl: &str) -> Result<(), anyhow::Error> { + let (client, driver) = WebSocketClient::new(wsurl).await?; + + let driver_handle = tokio::spawn(async move { driver.run().await }); + + // Subscription functionality + let mut subs = client.subscribe(EventType::NewBlock.into()).await?; + + // Wait 2 NewBlock events + let mut ev_count = 2_i32; + + while let Some(res) = subs.next().await { + let _ev = res?; + ev_count -= 1; + if ev_count == 0 { + break; + } + } + + // Signal to the driver to terminate. + client.close()?; + // Await the driver's termination to ensure proper connection closure. + let _ = driver_handle.await?; + + Ok(()) +} diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 835910f..39a7421 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -52,8 +52,7 @@ tendermint-light-client.workspace = true tendermint-rpc = { workspace = true, features=["websocket-client", "http-client"]} tm-prover = { workspace = true} -quartz-common = { workspace = true, features=["contract"]} +quartz-common = { workspace = true, features=["contract", "proto"]} quartz-tee-ra = { workspace = true} -mtcs-enclave = { workspace = true, optional = false} wasmd-client.workspace = true tempfile.workspace = true diff --git a/cli/src/handler/enclave_start.rs b/cli/src/handler/enclave_start.rs index 1e5426b..42db6fb 100644 --- a/cli/src/handler/enclave_start.rs +++ b/cli/src/handler/enclave_start.rs @@ -38,6 +38,10 @@ impl Handler for EnclaveStartRequest { trusted_height.to_string(), "--trusted-hash".to_string(), trusted_hash.to_string(), + "--node-url".to_string(), + config.node_url, + "--tx-sender".to_string(), + config.tx_sender, ]; // Run quartz enclave and block @@ -98,7 +102,7 @@ async fn create_mock_enclave_child( // 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")) + .manifest_path(enclave_dir.join("Cargo.toml")) .exec() .map_err(|e| Error::GenericErr(e.to_string()))? .root_package() diff --git a/cli/src/handler/handshake.rs b/cli/src/handler/handshake.rs index 2dcff62..bcc868a 100644 --- a/cli/src/handler/handshake.rs +++ b/cli/src/handler/handshake.rs @@ -1,4 +1,4 @@ -use std::{fs, str::FromStr}; +use std::str::FromStr; use anyhow::anyhow; use async_trait::async_trait; @@ -57,7 +57,7 @@ async fn handshake(args: HandshakeRequest, config: Config) -> Result Result Result, Self::Error>; diff --git a/core/quartz/src/error.rs b/core/quartz/src/error.rs new file mode 100644 index 0000000..c3ff641 --- /dev/null +++ b/core/quartz/src/error.rs @@ -0,0 +1,15 @@ +use std::io; + +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum QuartzError { + #[error("WebSocket error: {0}")] + WebSocket(#[from] tendermint_rpc::Error), + #[error("Tonic transport error: {0}")] + TonicTransport(#[from] tonic::transport::Error), + #[error("IO error: {0}")] + Io(#[from] io::Error), + #[error("Other error: {0}")] + Other(String), +} diff --git a/core/quartz/src/lib.rs b/core/quartz/src/lib.rs index cb840c8..8093bc5 100644 --- a/core/quartz/src/lib.rs +++ b/core/quartz/src/lib.rs @@ -13,5 +13,6 @@ )] pub mod attestor; +pub mod error; pub mod server; pub mod types; diff --git a/core/quartz/src/server.rs b/core/quartz/src/server.rs index ee3ebd7..8324635 100644 --- a/core/quartz/src/server.rs +++ b/core/quartz/src/server.rs @@ -1,4 +1,6 @@ use std::{ + convert::Infallible, + net::SocketAddr, sync::{Arc, Mutex}, time::Duration, }; @@ -7,6 +9,7 @@ use cw_proof::proof::{ cw::{CwProof, RawCwProof}, Proof, }; +use futures_util::StreamExt; use k256::ecdsa::SigningKey; use quartz_cw::{ msg::{ @@ -16,26 +19,156 @@ use quartz_cw::{ state::{Config, LightClientOpts, Nonce, Session}, }; use quartz_proto::quartz::{ - core_server::Core, InstantiateRequest as RawInstantiateRequest, - InstantiateResponse as RawInstantiateResponse, SessionCreateRequest as RawSessionCreateRequest, + core_server::{Core, CoreServer}, + InstantiateRequest as RawInstantiateRequest, InstantiateResponse as RawInstantiateResponse, + SessionCreateRequest as RawSessionCreateRequest, SessionCreateResponse as RawSessionCreateResponse, SessionSetPubKeyRequest as RawSessionSetPubKeyRequest, SessionSetPubKeyResponse as RawSessionSetPubKeyResponse, }; use rand::Rng; use serde::{Deserialize, Serialize}; +use tendermint::{block::Height, Hash}; use tendermint_light_client::{ light_client::Options, types::{LightBlock, TrustThreshold}, }; +use tendermint_rpc::{ + event::Event, + query::{EventType, Query}, + SubscriptionClient, WebSocketClient, +}; use tm_stateless_verifier::make_provider; -use tonic::{Request, Response, Result as TonicResult, Status}; +use tonic::{ + body::BoxBody, + codegen::http, + server::NamedService, + transport::{server::Router, Server}, + Request, Response, Result as TonicResult, Status, +}; +use tower::Service; use crate::{ - attestor::Attestor, + attestor::{Attestor, DefaultAttestor}, + error::QuartzError, types::{InstantiateResponse, SessionCreateResponse, SessionSetPubKeyResponse}, }; +/// Trait for Quartz enclaves to process on-chain events. +/// +/// Implementors of this trait should define how to process incoming WebSocket events, +/// using the provided `event` and `ws_config` parameters. +/// +/// # Arguments +/// +/// * `event` - The WebSocket event received from the Tendermint RPC server. +/// * `ws_config` - Configuration values used for handling the WebSocket events, +/// such as node URL, a signer for transactions, and trusted block information. +/// +/// # Returns +/// +/// An `anyhow::Result<()>` indicating success or failure in handling the event. +#[tonic::async_trait] +pub trait WebSocketHandler: Send + Sync + 'static { + async fn handle(&self, event: Event, ws_config: WsListenerConfig) -> anyhow::Result<()>; // TODO: replace anyhow +} + +#[derive(Debug, Clone)] +pub struct WsListenerConfig { + pub node_url: String, + pub chain_id: String, + pub tx_sender: String, + pub trusted_hash: Hash, + pub trusted_height: Height, +} + +/// A trait for wrapping a tonic service with the gRPC server handler +pub trait IntoServer { + type Server; + + fn into_server(self) -> Self::Server; +} + +pub struct QuartzServer { + pub router: Router, + ws_handlers: Vec>, + pub ws_config: WsListenerConfig, +} + +impl QuartzServer { + pub fn new( + config: Config, + sk: Arc>>, + attestor: DefaultAttestor, + ws_config: WsListenerConfig, + ) -> Self { + let core_service = CoreServer::new(CoreService::new(config, sk.clone(), attestor.clone())); + + Self { + router: Server::builder().add_service(core_service), + ws_handlers: Vec::new(), + ws_config, + } + } + + pub fn add_service(mut self, service: S) -> Self + where + S: IntoServer + WebSocketHandler + Clone, + S::Server: Service< + http::request::Request, + Response = http::response::Response, + Error = Infallible, + > + NamedService + + Clone + + Send + + 'static, + >>::Future: Send + 'static, + { + self.ws_handlers.push(Box::new(service.clone())); + + let tonic_server = service.into_server(); + self.router = self.router.add_service(tonic_server); + + self + } + + pub async fn serve(self, addr: SocketAddr) -> Result<(), QuartzError> { + // Launch all WebSocket handlers as separate Tokio tasks + tokio::spawn(async move { + if let Err(e) = Self::websocket_events_listener(&self.ws_handlers, self.ws_config).await + { + eprintln!("Error in WebSocket event handler: {:?}", e); + } + }); + + Ok(self.router.serve(addr).await?) + } + + async fn websocket_events_listener( + ws_handlers: &Vec>, + ws_config: WsListenerConfig, + ) -> Result<(), QuartzError> { + let wsurl = format!("ws://{}/websocket", ws_config.node_url); + let (client, driver) = WebSocketClient::new(wsurl.as_str()).await.unwrap(); + let driver_handle = tokio::spawn(async move { driver.run().await }); + let mut subs = client.subscribe(Query::from(EventType::Tx)).await.unwrap(); + + while let Some(Ok(event)) = subs.next().await { + for handler in ws_handlers { + if let Err(e) = handler.handle(event.clone(), ws_config.clone()).await { + eprintln!("Error in event handler: {:?}", e); + } + } + } + + // Close connection + client.close()?; + let _ = driver_handle.await; + + Ok(()) + } +} + #[derive(Clone, Debug)] pub struct CoreService { config: Config, diff --git a/cosmwasm/packages/wasmd-client/Cargo.toml b/cosmwasm/packages/wasmd-client/Cargo.toml index 49755a2..f1317b1 100644 --- a/cosmwasm/packages/wasmd-client/Cargo.toml +++ b/cosmwasm/packages/wasmd-client/Cargo.toml @@ -12,9 +12,9 @@ authors.workspace = true path="src/lib.rs" [dependencies] -anyhow.workspace = true cosmrs.workspace = true hex.workspace = true reqwest.workspace = true serde.workspace = true -serde_json.workspace = true \ No newline at end of file +serde_json.workspace = true +anyhow.workspace = true \ No newline at end of file diff --git a/utils/tm-prover/src/config.rs b/utils/tm-prover/src/config.rs index 5d7dcdd..45af46e 100644 --- a/utils/tm-prover/src/config.rs +++ b/utils/tm-prover/src/config.rs @@ -1,4 +1,4 @@ -use std::{num::ParseIntError, path::PathBuf, str::FromStr}; +use std::{num::ParseIntError, str::FromStr}; use clap::Parser; use color_eyre::eyre::{eyre, Result}; @@ -82,7 +82,6 @@ impl Default for Config { trusting_period: 1209600u64, max_clock_drift: 5u64, max_block_lag: 5u64, - trace_file: None, verbose: Verbosity::default(), contract_address: "wasm14qdftsfk6fwn40l0xmruga08xlczl4g05npy70" .parse() @@ -131,10 +130,6 @@ pub struct Config { #[clap(long, default_value = "5")] pub max_block_lag: u64, - /// Output file to store light client proof (AKA verification trace) - #[clap(long)] - pub trace_file: Option, - /// Increase verbosity #[clap(flatten)] pub verbose: Verbosity, diff --git a/utils/tm-prover/src/main.rs b/utils/tm-prover/src/main.rs index 2f2b3f0..b86fe45 100644 --- a/utils/tm-prover/src/main.rs +++ b/utils/tm-prover/src/main.rs @@ -19,5 +19,8 @@ async fn main() -> Result<()> { .finish() .init(); - prove(args).await + let proof = prove(args).await?; + println!("{:?}", proof); + + Ok(()) } diff --git a/utils/tm-prover/src/prover.rs b/utils/tm-prover/src/prover.rs index 422030f..37f2503 100644 --- a/utils/tm-prover/src/prover.rs +++ b/utils/tm-prover/src/prover.rs @@ -1,5 +1,5 @@ #![deny( - warnings, + // warnings, trivial_casts, trivial_numeric_casts, unused_import_braces, @@ -7,12 +7,7 @@ )] #![forbid(unsafe_code)] -use std::{ - fs::File, - io::{BufWriter, Write}, - path::PathBuf, - time::Duration, -}; +use std::time::Duration; use color_eyre::{ eyre::{eyre, Result}, @@ -49,12 +44,11 @@ pub async fn prove( trusting_period, max_clock_drift, max_block_lag, - trace_file, verbose: _, contract_address, storage_key, }: TmProverConfig, -) -> Result<()> { +) -> Result { let options = Options { trust_threshold, trusting_period: Duration::from_secs(trusting_period), @@ -134,31 +128,18 @@ pub async fn prove( .verify(latest_app_hash.clone().into()) .map_err(|e: ProofError| eyre!(e))?; - if let Some(trace_file) = trace_file { - // replace the last block in the trace (i.e. the (latest - 1) block) with the latest block - // we don't actually verify the latest block because it will be verified on the other side - let latest_block = provider.fetch_light_block(latest_height)?; - let _ = primary_trace.pop(); - primary_trace.push(latest_block); + // Replace the last block in the trace (i.e., the (latest - 1) block) with the latest block + // We don't actually verify the latest block because it will be verified on the other side + let latest_block = provider.fetch_light_block(latest_height)?; + let _ = primary_trace.pop(); + primary_trace.push(latest_block); - let output = ProofOutput { - light_client_proof: primary_trace, - merkle_proof: proof.into(), - }; - write_proof_to_file(trace_file, output).await?; + let output = ProofOutput { + light_client_proof: primary_trace, + merkle_proof: proof.into(), }; - Ok(()) -} - -async fn write_proof_to_file(trace_file: PathBuf, output: ProofOutput) -> Result<()> { - info!("Writing proof to output file ({})", trace_file.display()); - - let file = File::create(trace_file)?; - let mut writer = BufWriter::new(file); - serde_json::to_writer(&mut writer, &output)?; - writer.flush()?; - Ok(()) + Ok(output) } async fn run_detector(