Skip to content

Commit 79a9c23

Browse files
authored
Host-enclave separation & redesign (#283)
1 parent b22e026 commit 79a9c23

36 files changed

+2516
-1695
lines changed

Cargo.lock

Lines changed: 16 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ authors = ["Informal Systems <[email protected]>"]
2424
# external
2525
anyhow = { version = "1.0.86", features = ["std", "backtrace"] }
2626
async-trait = { version = "0.1.79", default-features = false }
27+
bincode = { version = "2.0.0-rc.3", default-features = false, features = ["alloc"] }
2728
bip32 = { version = "0.5.1", default-features = false, features = [
2829
"alloc",
2930
"secp256k1",
@@ -47,6 +48,7 @@ k256 = { version = "0.13.2", default-features = false, features = [
4748
"ecdsa",
4849
"alloc",
4950
] }
51+
log = { version = "0.4.25", default-features = false }
5052
num-bigint = { version = "0.4.4", default-features = false }
5153
p256 = { version = "0.13.2", default-features = false }
5254
prost = { version = "0.13.1", default-features = false }

crates/enclave/core/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,16 @@ mock-sgx = ["quartz-contract-core/mock-sgx"]
2121
# external
2222
anyhow.workspace = true
2323
async-trait.workspace = true
24+
bincode.workspace = true
2425
sha2 = { workspace = true }
2526
clap.workspace = true
2627
color-eyre.workspace = true
28+
displaydoc.workspace = true
2729
ecies.workspace = true
2830
futures-util.workspace = true
2931
hex.workspace = true
3032
k256.workspace = true
33+
log.workspace = true
3134
rand.workspace = true
3235
reqwest = { workspace = true, features = ["blocking"] }
3336
serde.workspace = true
@@ -49,8 +52,10 @@ tendermint-light-client.workspace = true
4952
tendermint-rpc = { workspace = true, features = ["websocket-client", "http-client"] }
5053

5154
# quartz
55+
cw-client.workspace = true
5256
quartz-cw-proof.workspace = true
5357
quartz-contract-core.workspace = true
5458
quartz-proto.workspace = true
59+
quartz-tm-prover.workspace = true
5560
quartz-tee-ra.workspace = true
5661
quartz-tm-stateless-verifier.workspace = true
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
use std::fmt::Display;
2+
3+
use serde::{de::DeserializeOwned, Serialize};
4+
5+
pub mod default;
6+
7+
#[async_trait::async_trait]
8+
pub trait ChainClient: Send + Sync + 'static {
9+
type Contract: Send + Sync + 'static;
10+
type Error: Display + Send + Sync + 'static;
11+
type Proof: Serialize + Send + Sync + 'static;
12+
type Query: Send + Sync + 'static;
13+
type TxConfig: Send + Sync + 'static;
14+
type TxOutput: Send + Sync + 'static;
15+
16+
async fn query_contract<R: DeserializeOwned + Default + Send>(
17+
&self,
18+
contract: &Self::Contract,
19+
query: impl Into<Self::Query> + Send,
20+
) -> Result<R, Self::Error>;
21+
22+
async fn existence_proof(
23+
&self,
24+
contract: &Self::Contract,
25+
storage_key: &str,
26+
) -> Result<Self::Proof, Self::Error>;
27+
28+
async fn send_tx<T: Serialize + Send + Sync>(
29+
&self,
30+
contract: &Self::Contract,
31+
tx: T,
32+
config: Self::TxConfig,
33+
) -> Result<Self::TxOutput, Self::Error>;
34+
35+
async fn wait_for_blocks(&self, blocks: u8) -> Result<(), Self::Error>;
36+
}
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
use anyhow::anyhow;
2+
use cosmrs::{crypto::secp256k1::SigningKey, AccountId};
3+
use cw_client::{CwClient, GrpcClient};
4+
use futures_util::StreamExt;
5+
use quartz_tm_prover::{
6+
config::{Config as TmProverConfig, ProofOutput},
7+
prover::prove,
8+
};
9+
use reqwest::Url;
10+
use serde::{de::DeserializeOwned, Serialize};
11+
use serde_json::{json, Value};
12+
use tendermint::{block::Height, chain::Id as TmChainId, Hash};
13+
use tendermint_rpc::{query::EventType, SubscriptionClient, WebSocketClient};
14+
15+
use crate::chain_client::ChainClient;
16+
17+
pub struct DefaultChainClient {
18+
chain_id: TmChainId,
19+
grpc_client: GrpcClient,
20+
node_url: Url,
21+
ws_url: Url,
22+
trusted_height: Height,
23+
trusted_hash: Hash,
24+
}
25+
26+
impl DefaultChainClient {
27+
pub fn new(
28+
chain_id: TmChainId,
29+
signer: SigningKey,
30+
grpc_url: Url,
31+
node_url: Url,
32+
ws_url: Url,
33+
trusted_height: Height,
34+
trusted_hash: Hash,
35+
) -> Self {
36+
DefaultChainClient {
37+
chain_id,
38+
grpc_client: GrpcClient::new(signer, grpc_url),
39+
node_url,
40+
ws_url,
41+
trusted_height,
42+
trusted_hash,
43+
}
44+
}
45+
}
46+
47+
pub enum Query {
48+
Json(Value),
49+
String(String),
50+
}
51+
52+
impl From<Value> for Query {
53+
fn from(value: Value) -> Self {
54+
Self::Json(value)
55+
}
56+
}
57+
58+
impl From<String> for Query {
59+
fn from(value: String) -> Self {
60+
Self::String(value)
61+
}
62+
}
63+
64+
#[async_trait::async_trait]
65+
impl ChainClient for DefaultChainClient {
66+
type Contract = AccountId;
67+
type Error = anyhow::Error;
68+
type Proof = ProofOutput;
69+
type Query = Query;
70+
type TxConfig = DefaultTxConfig;
71+
type TxOutput = String;
72+
73+
async fn query_contract<R: DeserializeOwned + Default + Send>(
74+
&self,
75+
contract: &Self::Contract,
76+
query: impl Into<Self::Query> + Send,
77+
) -> Result<R, Self::Error> {
78+
match query.into() {
79+
Query::Json(q) => self.grpc_client.query_smart(contract, q).await,
80+
Query::String(q) => self.grpc_client.query_raw(contract, q).await,
81+
}
82+
}
83+
84+
async fn existence_proof(
85+
&self,
86+
contract: &Self::Contract,
87+
storage_key: &str,
88+
) -> Result<Self::Proof, Self::Error> {
89+
let prover_config = TmProverConfig {
90+
primary: self.node_url.as_str().parse()?,
91+
witnesses: self.node_url.as_str().parse()?,
92+
trusted_height: self.trusted_height,
93+
trusted_hash: self.trusted_hash,
94+
verbose: "1".parse()?,
95+
contract_address: contract.clone(),
96+
storage_key: storage_key.to_string(),
97+
chain_id: self.chain_id.to_string(),
98+
..Default::default()
99+
};
100+
101+
let proof_output = tokio::task::spawn_blocking(move || {
102+
// Create a new runtime inside the blocking thread.
103+
let rt = tokio::runtime::Runtime::new()?;
104+
rt.block_on(async {
105+
prove(prover_config)
106+
.await
107+
.map_err(|report| anyhow!("Tendermint prover failed. Report: {}", report))
108+
})
109+
})
110+
.await??; // Handle both JoinError and your custom error
111+
Ok(proof_output)
112+
}
113+
114+
async fn send_tx<T: Serialize + Send + Sync>(
115+
&self,
116+
contract: &Self::Contract,
117+
tx: T,
118+
config: Self::TxConfig,
119+
) -> Result<Self::TxOutput, Self::Error> {
120+
self.grpc_client
121+
.tx_execute(
122+
contract,
123+
&self.chain_id,
124+
config.gas,
125+
"",
126+
json!(tx),
127+
&config.amount,
128+
)
129+
.await
130+
}
131+
132+
async fn wait_for_blocks(&self, _blocks: u8) -> Result<(), Self::Error> {
133+
let (client, driver) = WebSocketClient::new(self.ws_url.to_string().as_str()).await?;
134+
135+
let driver_handle = tokio::spawn(async move { driver.run().await });
136+
137+
// Subscription functionality
138+
let mut subs = client.subscribe(EventType::NewBlock.into()).await?;
139+
140+
// Wait 2 NewBlock events
141+
let mut ev_count = 2_i32;
142+
143+
while let Some(res) = subs.next().await {
144+
let _ev = res?;
145+
ev_count -= 1;
146+
if ev_count == 0 {
147+
break;
148+
}
149+
}
150+
151+
// Signal to the driver to terminate.
152+
client.close()?;
153+
// Await the driver's termination to ensure proper connection closure.
154+
let _ = driver_handle.await?;
155+
156+
Ok(())
157+
}
158+
}
159+
160+
pub struct DefaultTxConfig {
161+
pub gas: u64,
162+
pub amount: String,
163+
}

crates/enclave/core/src/error.rs

Lines changed: 0 additions & 15 deletions
This file was deleted.

crates/enclave/core/src/event.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
use anyhow::{anyhow, Error as AnyhowError};
2+
use cosmrs::AccountId;
3+
use tendermint_rpc::event::Event as TmEvent;
4+
5+
use crate::{chain_client::ChainClient, handler::Handler};
6+
7+
#[derive(Clone, Debug)]
8+
pub struct QuartzEvent<E> {
9+
pub contract: AccountId,
10+
inner: E,
11+
}
12+
13+
impl<E> TryFrom<TmEvent> for QuartzEvent<E>
14+
where
15+
E: TryFrom<TmEvent, Error = AnyhowError>,
16+
{
17+
type Error = AnyhowError;
18+
19+
fn try_from(event: TmEvent) -> Result<Self, Self::Error> {
20+
let Some(events) = &event.events else {
21+
return Err(anyhow!("no events in tx"));
22+
};
23+
24+
let contract = events
25+
.get("execute._contract_address")
26+
.ok_or_else(|| anyhow!("missing execute._contract_address in events"))?
27+
.first()
28+
.ok_or_else(|| anyhow!("execute._contract_address is empty"))?
29+
.parse::<AccountId>()
30+
.map_err(|e| anyhow!("failed to parse contract address: {}", e))?;
31+
32+
Ok(QuartzEvent {
33+
contract,
34+
inner: event.try_into()?,
35+
})
36+
}
37+
}
38+
39+
#[async_trait::async_trait]
40+
impl<C, E> Handler<C> for QuartzEvent<E>
41+
where
42+
C: ChainClient<Contract = AccountId>,
43+
E: Handler<C, Error = AnyhowError>,
44+
{
45+
type Error = AnyhowError;
46+
type Response = <E as Handler<C>>::Response;
47+
48+
async fn handle(self, ctx: &C) -> Result<Self::Response, Self::Error> {
49+
self.inner.handle(ctx).await
50+
}
51+
}

0 commit comments

Comments
 (0)