feat: move proxy logic into enclave (#174)

This commit is contained in:
Daniel Gushchyan 2024-09-18 13:04:33 -07:00 committed by GitHub
parent 628d7b4596
commit 69c1f63114
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 1791 additions and 141 deletions

33
Cargo.lock generated
View file

@ -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"

View file

@ -22,7 +22,7 @@ authors = ["Informal Systems <hello@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"] }

View file

@ -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"

View file

@ -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<TrustThreshold> {
@ -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,
}

View file

@ -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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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?;

View file

@ -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<A: Attestor> IntoServer for MtcsService<A> {
type Server = ClearingServer<MtcsService<A>>;
fn into_server(self) -> Self::Server {
ClearingServer::new(self)
}
}
#[derive(Clone, Debug)]
pub struct MtcsService<A> {
config: Config, // TODO: this config is not used anywhere
@ -59,20 +69,20 @@ where
request: Request<RunClearingRequest>,
) -> TonicResult<Response<RunClearingResponse>> {
// Light client check
let message: ProofOfPublication<RunClearingMessage> = {
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<LiquiditySource> =
message.liquidity_sources.into_iter().collect();

View file

@ -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<A: Attestor> WebSocketHandler for MtcsService<A> {
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::<AccountId>()
.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<A: Attestor>(
client: &MtcsService<A>,
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<SubmitSetoffsMsg, Vec<u8>> =
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::<RawMockAttestation>(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<RunClearingMessage> {
// Get epoch counter
let resp: QueryResult<String> = 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::<usize>()?;
if epoch_counter > 1 {
epoch_counter -= 1;
}
// TODO: replace with tracer log here
// println!("epoch: {}", epoch_counter);
// Get obligations
let resp: QueryResult<String> = 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<HexBinary, HexBinary> =
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<GetLiquiditySourcesResponse> = 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<u8>,
// user: &str,
// ) -> Result<EpidAttestation, anyhow::Error> {
// 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(&quote_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", &quote_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 }))
// }

View file

@ -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"] }

View file

@ -230,6 +230,8 @@ pub mod execute {
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
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<Vec<Request>> {
Ok(REQUESTS.load(deps.storage)?)
}
pub fn get_state(deps: Deps) -> StdResult<HexBinary> {
Ok(STATE.load(deps.storage)?)
}
}

View file

@ -4,7 +4,7 @@ use quartz_common::contract::{
prelude::*,
};
type AttestedMsg<M, RA = RawDefaultAttestation> = RawAttested<RawAttestedMsgSansHandler<M>, RA>;
pub type AttestedMsg<M, RA = RawDefaultAttestation> = RawAttested<RawAttestedMsgSansHandler<M>, RA>;
#[cw_serde]
pub struct InstantiateMsg<RA = RawDefaultAttestation> {
@ -15,6 +15,8 @@ pub struct InstantiateMsg<RA = RawDefaultAttestation> {
#[cw_serde]
pub enum QueryMsg {
GetBalance { address: String },
GetRequests {},
GetState {},
}
#[cw_serde]

View file

@ -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"

View file

@ -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"

View file

@ -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<TrustThreshold> {
@ -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")

View file

@ -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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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?;

View file

@ -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<T> {
inner: tonic::client::Grpc<T>,
}
impl EventListenerClient<tonic::transport::Channel> {
/// Attempt to create a new client by connecting to a given endpoint.
pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error>
where
D: TryInto<tonic::transport::Endpoint>,
D::Error: Into<StdError>,
{
let conn = tonic::transport::Endpoint::new(dst)?.connect().await?;
Ok(Self::new(conn))
}
}
impl<T> EventListenerClient<T>
where
T: tonic::client::GrpcService<tonic::body::BoxBody>,
T::Error: Into<StdError>,
T::ResponseBody: Body<Data = Bytes> + Send + 'static,
<T::ResponseBody as Body>::Error: Into<StdError> + 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<F>(
inner: T,
interceptor: F,
) -> EventListenerClient<InterceptedService<T, F>>
where
F: tonic::service::Interceptor,
T::ResponseBody: Default,
T: tonic::codegen::Service<
http::Request<tonic::body::BoxBody>,
Response = http::Response<
<T as tonic::client::GrpcService<tonic::body::BoxBody>>::ResponseBody,
>,
>,
<T as tonic::codegen::Service<
http::Request<tonic::body::BoxBody>,
>>::Error: Into<StdError> + 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<super::ListenRequest>,
) -> std::result::Result<tonic::Response<super::ListenResponse>, 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<super::ListenRequest>,
) -> std::result::Result<tonic::Response<super::ListenResponse>, tonic::Status>;
}
#[derive(Debug)]
pub struct EventListenerServer<T: EventListener> {
inner: Arc<T>,
accept_compression_encodings: EnabledCompressionEncodings,
send_compression_encodings: EnabledCompressionEncodings,
max_decoding_message_size: Option<usize>,
max_encoding_message_size: Option<usize>,
}
impl<T: EventListener> EventListenerServer<T> {
pub fn new(inner: T) -> Self {
Self::from_arc(Arc::new(inner))
}
pub fn from_arc(inner: Arc<T>) -> 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<F>(
inner: T,
interceptor: F,
) -> InterceptedService<Self, F>
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<T, B> tonic::codegen::Service<http::Request<B>> for EventListenerServer<T>
where
T: EventListener,
B: Body + Send + 'static,
B::Error: Into<StdError> + Send + 'static,
{
type Response = http::Response<tonic::body::BoxBody>;
type Error = std::convert::Infallible;
type Future = BoxFuture<Self::Response, Self::Error>;
fn poll_ready(
&mut self,
_cx: &mut Context<'_>,
) -> Poll<std::result::Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: http::Request<B>) -> Self::Future {
match req.uri().path() {
"/transfers.EventListener/Listen" => {
#[allow(non_camel_case_types)]
struct ListenSvc<T: EventListener>(pub Arc<T>);
impl<
T: EventListener,
> tonic::server::UnaryService<super::ListenRequest>
for ListenSvc<T> {
type Response = super::ListenResponse;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
fn call(
&mut self,
request: tonic::Request<super::ListenRequest>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as EventListener>::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<T: EventListener> Clone for EventListenerServer<T> {
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<T: EventListener> tonic::server::NamedService for EventListenerServer<T> {
const NAME: &'static str = "transfers.EventListener";
}
}

View file

@ -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<A: Attestor> IntoServer for TransfersService<A> {
type Server = SettlementServer<TransfersService<A>>;
fn into_server(self) -> Self::Server {
SettlementServer::new(self)
}
}
pub type RawCipherText = HexBinary;
#[derive(Clone, Debug)]
@ -34,17 +46,17 @@ pub struct TransfersService<A> {
attestor: A,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
pub struct UpdateRequestMessage {
state: HexBinary,
requests: Vec<TransfersRequest>,
pub state: HexBinary,
pub requests: Vec<TransfersRequest>,
}
#[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)]

View file

@ -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<A: Attestor> WebSocketHandler for TransfersService<A> {
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::<AccountId>()
.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::<AccountId>()
.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<A: Attestor>(
client: &TransfersService<A>,
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<Vec<TransferRequest>> = wasmd_client
.query_smart(contract, json!(GetRequests {}))
.map_err(|e| anyhow!("Problem querying epoch: {}", e))?;
let requests = resp.data;
let resp: QueryResult<HexBinary> = 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<UpdateMsg, HexBinary> =
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::<RawMockAttestation>(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<A: Attestor>(
client: &TransfersService<A>,
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<HexBinary> = 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<QueryResponseMsg, HexBinary> =
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::<RawMockAttestation>(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(())
}

View file

@ -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

View file

@ -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()

View file

@ -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<String, any
info!("Running SessionCreate");
let res: serde_json::Value = RelayMessage::SessionCreate
let res = RelayMessage::SessionCreate
.run_relay(config.enclave_rpc(), config.mock_sgx)
.await?;
@ -82,33 +82,26 @@ async fn handshake(args: HandshakeRequest, config: Config) -> Result<String, any
info!("Waiting 2 blocks for light client proof");
two_block_waitoor(&wsurl).await?;
let proof_path = config.cache_dir()?.join("light-client-proof.json");
debug!("Proof path: {:?}", proof_path.to_str());
// Call tm prover with trusted hash and height
let prover_config = TmProverConfig {
primary: httpurl.as_str().parse()?,
witnesses: httpurl.as_str().parse()?,
trusted_height,
trusted_hash,
trace_file: Some(proof_path.clone()),
verbose: "1".parse()?, // TODO: both tm-prover and cli define the same Verbosity struct. Need to define this once and import
contract_address: args.contract.clone(),
storage_key: "quartz_session".to_string(),
chain_id: config.chain_id.to_string(),
..Default::default()
};
debug!("config: {:?}", prover_config);
if let Err(report) = prove(prover_config).await {
return Err(anyhow!("Tendermint prover failed. Report: {}", report));
}
// Read proof file
let proof = fs::read_to_string(proof_path.as_path())?;
let proof_output = prove(prover_config)
.await
.map_err(|report| anyhow!("Tendermint prover failed. Report: {}", report))?;
// Execute SessionSetPubKey on enclave
info!("Running SessionSetPubKey");
let res: serde_json::Value = RelayMessage::SessionSetPubKey(proof.trim().to_string())
let res = RelayMessage::SessionSetPubKey(serde_json::to_string(&proof_output)?)
.run_relay(config.enclave_rpc(), config.mock_sgx)
.await?;

View file

@ -16,23 +16,29 @@ mock-sgx = ["quartz-cw/mock-sgx"]
[dependencies]
# external
anyhow.workspace = true
async-trait.workspace = true
sha2 = { workspace = true }
clap.workspace = true
color-eyre.workspace = true
ecies.workspace = true
futures-util.workspace = true
hex.workspace = true
k256.workspace = true
rand.workspace = true
serde.workspace = true
serde_json.workspace = true
thiserror.workspace = true
tonic.workspace = true
tokio.workspace = true
tower.workspace = true
# cosmos
cosmrs.workspace = true
cosmwasm-std.workspace = true
tendermint.workspace = true
tendermint-light-client.workspace = true
tendermint-rpc = { workspace = true, features=["websocket-client", "http-client"] }
# quartz
cw-proof.workspace = true
@ -40,4 +46,4 @@ mtcs.workspace = true
quartz-cw.workspace = true
quartz-proto.workspace = true
quartz-tee-ra.workspace = true
tm-stateless-verifier.workspace = true
tm-stateless-verifier.workspace = true

View file

@ -15,7 +15,7 @@ pub type DefaultAttestor = EpidAttestor;
pub type DefaultAttestor = MockAttestor;
/// The trait defines the interface for generating attestations from within an enclave.
pub trait Attestor {
pub trait Attestor: Send + Sync + 'static {
type Error: ToString;
fn quote(&self, user_data: impl HasUserData) -> Result<Vec<u8>, Self::Error>;

15
core/quartz/src/error.rs Normal file
View file

@ -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),
}

View file

@ -13,5 +13,6 @@
)]
pub mod attestor;
pub mod error;
pub mod server;
pub mod types;

View file

@ -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<Box<dyn WebSocketHandler>>,
pub ws_config: WsListenerConfig,
}
impl QuartzServer {
pub fn new(
config: Config,
sk: Arc<Mutex<Option<SigningKey>>>,
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<S>(mut self, service: S) -> Self
where
S: IntoServer + WebSocketHandler + Clone,
S::Server: Service<
http::request::Request<BoxBody>,
Response = http::response::Response<BoxBody>,
Error = Infallible,
> + NamedService
+ Clone
+ Send
+ 'static,
<S::Server as Service<http::request::Request<BoxBody>>>::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<Box<dyn WebSocketHandler>>,
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<A> {
config: Config,

View file

@ -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
serde_json.workspace = true
anyhow.workspace = true

View file

@ -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<PathBuf>,
/// Increase verbosity
#[clap(flatten)]
pub verbose: Verbosity,

View file

@ -19,5 +19,8 @@ async fn main() -> Result<()> {
.finish()
.init();
prove(args).await
let proof = prove(args).await?;
println!("{:?}", proof);
Ok(())
}

View file

@ -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<ProofOutput> {
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(