Skip to content

Commit 8344617

Browse files
committed
wip
1 parent ca42d6e commit 8344617

File tree

7 files changed

+283
-3
lines changed

7 files changed

+283
-3
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ members = [
207207
"voyager/plugins/client-update/state-lens",
208208
"voyager/plugins/client-update/sui",
209209
"voyager/plugins/client-update/trusted-mpt",
210+
"voyager/plugins/client-update/attested",
210211

211212
"voyager/plugins/periodic-client-update",
212213

cosmwasm/ibc-union/lightclient/attested/src/state.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,10 @@ impl KeyCodec<(String, u64)> for HeightTimestamps {
9696

9797
fn decode_key(raw: &Bytes) -> StdResult<(String, u64)> {
9898
if raw.len() < 8 {
99-
return Err(StdError::generic_err(format!(
99+
Err(StdError::generic_err(format!(
100100
"invalid key: expected at least 8 bytes, found {} (raw: {raw})",
101101
raw.len()
102-
)));
102+
)))
103103
} else {
104104
let height = raw[raw.len() - 8..]
105105
.try_into()

lib/voyager-primitives/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,9 @@ impl ClientType {
178178
/// [L2 settlement]: https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts-bedrock
179179
pub const OPTIMISM: &'static str = "optimism";
180180

181+
/// An attested client.
182+
pub const ATTESTED: &'static str = "attested";
183+
181184
// lots more to come - near, linea, polygon - stay tuned
182185
}
183186

voyager/plugins/attestor/evm/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,7 @@ impl Module {
436436
let tx_client = TxClient::new(signer, &self.cosmos_client, &self.gas_config);
437437

438438
let attestation = Attestation {
439-
chain_id: self.chain_id.clone().to_string(),
439+
chain_id: self.chain_id.to_string(),
440440
height,
441441
timestamp: Timestamp::from_secs(timestamp),
442442
key: key.into(),
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
[package]
2+
name = "voyager-client-update-plugin-attested"
3+
version = "0.0.0"
4+
5+
authors = { workspace = true }
6+
edition = { workspace = true }
7+
license-file = { workspace = true }
8+
publish = { workspace = true }
9+
repository = { workspace = true }
10+
11+
[lints]
12+
workspace = true
13+
14+
[dependencies]
15+
attested-light-client = { workspace = true }
16+
attested-light-client-types = { workspace = true, features = ["serde"] }
17+
cometbft-rpc = { workspace = true }
18+
embed-commit = { workspace = true }
19+
ibc-union-spec = { workspace = true, features = ["serde"] }
20+
jsonrpsee = { workspace = true, features = ["macros", "server", "tracing"] }
21+
prost = { workspace = true }
22+
protos = { workspace = true }
23+
serde = { workspace = true, features = ["derive"] }
24+
serde_json = { workspace = true }
25+
thiserror = { workspace = true }
26+
tokio = { workspace = true }
27+
tracing = { workspace = true }
28+
unionlabs = { workspace = true }
29+
voyager-sdk = { workspace = true }
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
use std::collections::VecDeque;
2+
3+
use attested_light_client::msg::QueryMsg;
4+
use attested_light_client_types::Header;
5+
use ibc_union_spec::Timestamp;
6+
use jsonrpsee::{
7+
Extensions,
8+
core::{RpcResult, async_trait},
9+
types::ErrorObject,
10+
};
11+
use protos::cosmwasm::wasm::v1::{QuerySmartContractStateRequest, QuerySmartContractStateResponse};
12+
use serde::{Deserialize, Serialize};
13+
use tracing::instrument;
14+
use unionlabs::{
15+
ErrorReporter,
16+
ibc::core::client::height::Height,
17+
never::Never,
18+
primitives::{Bech32, H256},
19+
};
20+
use voyager_sdk::{
21+
DefaultCmd, anyhow,
22+
hook::UpdateHook,
23+
into_value,
24+
message::{
25+
PluginMessage, VoyagerMessage,
26+
call::Call,
27+
data::{Data, DecodedHeaderMeta, OrderedHeaders},
28+
},
29+
plugin::Plugin,
30+
primitives::{ChainId, ClientType},
31+
rpc::{FATAL_JSONRPC_ERROR_CODE, PluginServer, types::PluginInfo},
32+
vm::{Op, Visit, data, pass::PassResult},
33+
};
34+
35+
use crate::call::{FetchUpdate, ModuleCall};
36+
37+
pub mod call {
38+
use serde::{Deserialize, Serialize};
39+
40+
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
41+
pub enum ModuleCall {
42+
FetchUpdate(FetchUpdate),
43+
}
44+
45+
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
46+
pub struct FetchUpdate {
47+
pub to: u64,
48+
}
49+
}
50+
51+
#[tokio::main(flavor = "multi_thread")]
52+
async fn main() {
53+
Module::run().await;
54+
}
55+
56+
#[derive(Debug, Clone)]
57+
pub struct Module {
58+
pub chain_id: ChainId,
59+
pub attestation_client_address: Bech32<H256>,
60+
pub cometbft_client: cometbft_rpc::Client,
61+
}
62+
63+
#[derive(Debug, Clone, Serialize, Deserialize)]
64+
#[serde(deny_unknown_fields)]
65+
pub struct Config {
66+
pub chain_id: ChainId,
67+
pub attestation_client_address: Bech32<H256>,
68+
pub rpc_url: String,
69+
}
70+
71+
impl Plugin for Module {
72+
type Call = ModuleCall;
73+
type Callback = Never;
74+
75+
type Config = Config;
76+
type Cmd = DefaultCmd;
77+
78+
async fn new(config: Self::Config) -> anyhow::Result<Self> {
79+
Ok(Self {
80+
chain_id: config.chain_id,
81+
attestation_client_address: config.attestation_client_address,
82+
cometbft_client: cometbft_rpc::Client::new(config.rpc_url).await?,
83+
})
84+
}
85+
86+
fn info(config: Self::Config) -> PluginInfo {
87+
PluginInfo {
88+
name: plugin_name(&config.chain_id),
89+
interest_filter: UpdateHook::filter(
90+
&config.chain_id,
91+
&ClientType::new(ClientType::ATTESTED),
92+
),
93+
}
94+
}
95+
96+
async fn cmd(_: Self::Config, cmd: Self::Cmd) {
97+
match cmd {}
98+
}
99+
}
100+
101+
#[async_trait]
102+
impl PluginServer<ModuleCall, Never> for Module {
103+
#[instrument(skip_all, fields(chain_id = %self.chain_id))]
104+
async fn run_pass(
105+
&self,
106+
_: &Extensions,
107+
msgs: Vec<Op<VoyagerMessage>>,
108+
) -> RpcResult<PassResult<VoyagerMessage>> {
109+
Ok(PassResult {
110+
optimize_further: vec![],
111+
ready: msgs
112+
.into_iter()
113+
.map(|mut op| {
114+
UpdateHook::new(
115+
&self.chain_id,
116+
&ClientType::new(ClientType::ATTESTED),
117+
|fetch| {
118+
Call::Plugin(PluginMessage::new(
119+
self.plugin_name(),
120+
ModuleCall::FetchUpdate(FetchUpdate {
121+
to: fetch.update_to.height(),
122+
}),
123+
))
124+
},
125+
)
126+
.visit_op(&mut op);
127+
128+
op
129+
})
130+
.enumerate()
131+
.map(|(i, op)| (vec![i], op))
132+
.collect(),
133+
})
134+
}
135+
136+
#[instrument(skip_all, fields(chain_id = %self.chain_id))]
137+
async fn call(&self, _: &Extensions, msg: ModuleCall) -> RpcResult<Op<VoyagerMessage>> {
138+
match msg {
139+
ModuleCall::FetchUpdate(FetchUpdate { to }) => {
140+
let timestamp = self.query_attested_timestamp_at_height(to).await?;
141+
142+
Ok(data(OrderedHeaders {
143+
headers: vec![(
144+
DecodedHeaderMeta {
145+
height: Height::new(to),
146+
},
147+
into_value(Header {
148+
height: to,
149+
timestamp,
150+
}),
151+
)],
152+
}))
153+
}
154+
}
155+
}
156+
157+
#[instrument(skip_all, fields(chain_id = %self.chain_id))]
158+
async fn callback(
159+
&self,
160+
_: &Extensions,
161+
cb: Never,
162+
_: VecDeque<Data>,
163+
) -> RpcResult<Op<VoyagerMessage>> {
164+
match cb {}
165+
}
166+
}
167+
168+
fn plugin_name(chain_id: &ChainId) -> String {
169+
pub const PLUGIN_NAME: &str = env!("CARGO_PKG_NAME");
170+
171+
format!("{PLUGIN_NAME}/{}", chain_id)
172+
}
173+
174+
impl Module {
175+
fn plugin_name(&self) -> String {
176+
plugin_name(&self.chain_id)
177+
}
178+
179+
async fn query_attested_timestamp_at_height(&self, height: u64) -> RpcResult<Timestamp> {
180+
let req = QuerySmartContractStateRequest {
181+
address: self.attestation_client_address.to_string(),
182+
query_data: serde_json::to_vec(&QueryMsg::TimestampAtHeight {
183+
chain_id: self.chain_id.to_string(),
184+
height,
185+
})
186+
.unwrap(),
187+
};
188+
189+
let raw = self
190+
.cometbft_client
191+
.grpc_abci_query::<_, QuerySmartContractStateResponse>(
192+
"/cosmwasm.wasm.v1.Query/SmartContractState",
193+
&req,
194+
None,
195+
false,
196+
)
197+
.await
198+
.map_err(|e| {
199+
ErrorObject::owned(
200+
-1,
201+
ErrorReporter(e).with_message("error fetching attested timestamp at height"),
202+
None::<()>,
203+
)
204+
})?
205+
.into_result()
206+
.map_err(|e| {
207+
ErrorObject::owned(
208+
-1,
209+
ErrorReporter(e).with_message("error fetching attested timestamp at height"),
210+
None::<()>,
211+
)
212+
})?
213+
.unwrap()
214+
.data;
215+
216+
Ok(serde_json::from_slice::<Option<Timestamp>>(&raw)
217+
.map_err(|e| {
218+
ErrorObject::owned(
219+
FATAL_JSONRPC_ERROR_CODE,
220+
ErrorReporter(e).with_message("height {height} has not been attested to"),
221+
None::<()>,
222+
)
223+
})?
224+
.unwrap())
225+
}
226+
}

0 commit comments

Comments
 (0)