Skip to content

Commit d1bff50

Browse files
authored
feat(attested-light-client): init (#5210)
2 parents eac6334 + 0f12070 commit d1bff50

File tree

20 files changed

+1242
-11
lines changed

20 files changed

+1242
-11
lines changed

Cargo.lock

Lines changed: 37 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: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ members = [
9090
"lib/movement-light-client-types",
9191
"lib/parlia-light-client-types",
9292
"lib/trusted-mpt-light-client-types",
93+
"lib/attested-light-client-types",
9394
"lib/linea-light-client-types",
9495
"lib/scroll-light-client-types",
9596
"lib/state-lens-ics23-mpt-light-client-types",
@@ -110,6 +111,7 @@ members = [
110111
# "cosmwasm/ibc-union/lightclient/movement",
111112
"cosmwasm/ibc-union/lightclient/parlia",
112113
"cosmwasm/ibc-union/lightclient/trusted-mpt",
114+
"cosmwasm/ibc-union/lightclient/attested",
113115
"cosmwasm/ibc-union/lightclient/state-lens-ics23-mpt",
114116
# "cosmwasm/ibc-union/lightclient/state-lens-ics23-smt",
115117
"cosmwasm/ibc-union/lightclient/state-lens-ics23-ics23",
@@ -390,13 +392,12 @@ ibc-union-msg = { path = "cosmwasm/ibc-union/core/msg", default-feature
390392

391393
frissitheto = { path = "lib/frissitheto", default-features = false }
392394

393-
access-manager = { path = "cosmwasm/access-manager", default-features = false }
394-
395395
lst = { path = "cosmwasm/lst", default-features = false }
396396

397-
cw-account = { path = "cosmwasm/cw-account", default-features = false }
398-
cw-escrow-vault = { path = "cosmwasm/cw-escrow-vault", default-features = false }
399-
cw-unionversal-token = { path = "cosmwasm/cw-unionversal-token", default-features = false }
397+
access-manager = { path = "cosmwasm/access-manager", default-features = false }
398+
399+
cw-account = { path = "cosmwasm/cw-account", default-features = false }
400+
cw-escrow-vault = { path = "cosmwasm/cw-escrow-vault", default-features = false }
400401

401402
cw20-base = { path = "cosmwasm/cw20-base", default-features = false }
402403
cw20-ctx = { path = "lib/cw20-ctx", default-features = false }
@@ -425,6 +426,7 @@ reconnecting-jsonrpc-ws-client = { path = "lib/reconnecting-jsonrpc-ws-client",
425426
ibc-classic-spec = { path = "lib/ibc-classic-spec", default-features = false }
426427
ibc-union-spec = { path = "lib/ibc-union-spec", default-features = false }
427428

429+
attested-light-client-types = { path = "lib/attested-light-client-types", default-features = false }
428430
movement-light-client-types = { path = "lib/movement-light-client-types", default-features = false }
429431
trusted-mpt-light-client-types = { path = "lib/trusted-mpt-light-client-types", default-features = false }
430432

cosmwasm/ibc-union/core/light-client-interface/src/lib.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,6 @@ pub enum IbcClientError<T: IbcClient> {
6363
ClientSpecific(T::Error),
6464
#[error("`ClientMessage` cannot be decoded ({data})", data = serde_utils::to_hex(.0))]
6565
InvalidClientMessage(Vec<u8>),
66-
#[error("caller `{0}` is not a whitelisted relayer")]
67-
UnauthorizedCaller(String),
6866
}
6967

7068
impl<T: IbcClient + 'static> From<IbcClientError<T>> for StdError {
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
[package]
2+
name = "attested-light-client"
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+
[lib]
15+
crate-type = ["cdylib", "rlib"]
16+
17+
[dependencies]
18+
attested-light-client-types = { workspace = true, features = ["serde", "ethabi", "bincode"] }
19+
bincode = { workspace = true, features = ["derive"] }
20+
cosmwasm-std = { workspace = true, features = ["abort"] }
21+
depolama = { workspace = true }
22+
embed-commit = { workspace = true }
23+
frissitheto = { workspace = true }
24+
ibc-union-light-client = { workspace = true }
25+
ibc-union-msg = { workspace = true }
26+
serde = { workspace = true, features = ["derive"] }
27+
thiserror = { workspace = true }
28+
unionlabs = { workspace = true, features = ["ethabi"] }
29+
30+
[dev-dependencies]
31+
base64 = { workspace = true }
32+
ed25519-dalek = "2.2.0"
33+
hex = { workspace = true }
34+
hex-literal = { workspace = true }
35+
serde_json = { workspace = true }
36+
37+
[features]
38+
library = []
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
use attested_light_client_types::{ClientState, ConsensusState, Header, StorageProof};
2+
use cosmwasm_std::{ensure, Addr, Deps, Empty};
3+
use depolama::StorageExt;
4+
use ibc_union_light_client::{
5+
spec::{Status, Timestamp},
6+
ClientCreationResult, IbcClient, IbcClientCtx, IbcClientError, StateUpdate,
7+
};
8+
use unionlabs::{encoding::Bincode, primitives::Bytes};
9+
10+
use crate::{
11+
errors::Error,
12+
state::{Attestations, HeightTimestamps},
13+
types::{AttestationKey, AttestationValue},
14+
};
15+
16+
pub enum AttestedLightClient {}
17+
18+
impl IbcClient for AttestedLightClient {
19+
type Error = Error;
20+
21+
type CustomQuery = Empty;
22+
23+
type Header = Header;
24+
25+
type Misbehaviour = ();
26+
27+
type ClientState = ClientState;
28+
29+
type ConsensusState = ConsensusState;
30+
31+
type StorageProof = StorageProof;
32+
33+
type Encoding = Bincode;
34+
35+
fn verify_membership(
36+
ctx: IbcClientCtx<Self>,
37+
height: u64,
38+
key: Vec<u8>,
39+
StorageProof {}: Self::StorageProof,
40+
value: Vec<u8>,
41+
) -> Result<(), IbcClientError<Self>> {
42+
verify_attestation(
43+
ctx.deps,
44+
height,
45+
key.into(),
46+
AttestationValue::Existence(value.into()),
47+
)
48+
.map_err(Into::into)
49+
}
50+
51+
fn verify_non_membership(
52+
ctx: IbcClientCtx<Self>,
53+
height: u64,
54+
key: Vec<u8>,
55+
StorageProof {}: Self::StorageProof,
56+
) -> Result<(), IbcClientError<Self>> {
57+
verify_attestation(ctx.deps, height, key.into(), AttestationValue::NonExistence)
58+
.map_err(Into::into)
59+
}
60+
61+
fn verify_header(
62+
ctx: IbcClientCtx<Self>,
63+
_caller: Addr,
64+
header: Self::Header,
65+
_relayer: Addr,
66+
) -> Result<StateUpdate<Self>, IbcClientError<Self>> {
67+
verify_header(ctx.deps, ctx.read_self_client_state()?, header).map_err(Into::into)
68+
}
69+
70+
fn misbehaviour(
71+
_ctx: IbcClientCtx<Self>,
72+
_caller: Addr,
73+
_misbehaviour: Self::Misbehaviour,
74+
_relayer: Addr,
75+
) -> Result<Self::ClientState, IbcClientError<Self>> {
76+
Err(Error::NoMisbehaviourInAttestedClient.into())
77+
}
78+
79+
fn status(ctx: IbcClientCtx<Self>, _client_state: &Self::ClientState) -> Status {
80+
let _ = ctx;
81+
82+
Status::Active
83+
}
84+
85+
fn verify_creation(
86+
_caller: Addr,
87+
_client_state: &Self::ClientState,
88+
_consensus_state: &Self::ConsensusState,
89+
_relayer: Addr,
90+
) -> Result<ClientCreationResult<Self>, IbcClientError<AttestedLightClient>> {
91+
Ok(ClientCreationResult::new())
92+
}
93+
94+
fn get_timestamp(consensus_state: &Self::ConsensusState) -> Timestamp {
95+
consensus_state.timestamp
96+
}
97+
98+
fn get_latest_height(client_state: &Self::ClientState) -> u64 {
99+
let ClientState::V1(client_state) = client_state;
100+
client_state.latest_height
101+
}
102+
103+
fn get_counterparty_chain_id(client_state: &Self::ClientState) -> String {
104+
let ClientState::V1(client_state) = client_state;
105+
client_state.chain_id.to_string()
106+
}
107+
}
108+
109+
pub fn verify_header(
110+
deps: Deps,
111+
client_state: ClientState,
112+
header: Header,
113+
) -> Result<StateUpdate<AttestedLightClient>, Error> {
114+
let ClientState::V1(mut client_state) = client_state;
115+
116+
let Header { height, timestamp } = header;
117+
118+
let attested_timestamp = deps.storage.read::<HeightTimestamps>(&height)?;
119+
120+
ensure!(
121+
attested_timestamp == timestamp,
122+
Error::InvalidTimestamp {
123+
height,
124+
attested_timestamp,
125+
timestamp
126+
}
127+
);
128+
129+
let mut update = StateUpdate::new(height, ConsensusState { timestamp });
130+
131+
if header.height > client_state.latest_height {
132+
client_state.latest_height = header.height;
133+
update = update.overwrite_client_state(ClientState::V1(client_state));
134+
}
135+
136+
Ok(update)
137+
}
138+
139+
pub fn verify_attestation(
140+
deps: Deps,
141+
height: u64,
142+
key: Bytes,
143+
value: AttestationValue,
144+
) -> Result<(), Error> {
145+
use AttestationValue::*;
146+
147+
let attested = deps
148+
.storage
149+
.maybe_read::<Attestations>(&AttestationKey {
150+
height,
151+
key: key.clone(),
152+
})?
153+
.ok_or_else(|| Error::AttestationNotFound {
154+
height,
155+
key: key.clone(),
156+
})?;
157+
158+
match (attested, value) {
159+
// membership
160+
(Existence(attested), Existence(value)) => {
161+
ensure!(
162+
value == attested,
163+
Error::InvalidAttestedValue {
164+
height,
165+
key,
166+
attested: Existence(attested),
167+
value: Existence(value),
168+
}
169+
);
170+
171+
Ok(())
172+
}
173+
174+
// non-membership
175+
(NonExistence, NonExistence) => Ok(()),
176+
177+
// invalid
178+
(attested @ Existence(_), value @ NonExistence)
179+
| (attested @ NonExistence, value @ Existence(_)) => Err(Error::InvalidAttestedValue {
180+
height,
181+
key,
182+
attested,
183+
value,
184+
}),
185+
}
186+
}

0 commit comments

Comments
 (0)