Skip to content

Commit 2337161

Browse files
authored
feat: upgradable cometbft client (#405)
* build upgraded client and consensus state paths * add tests for upgraded client and cons state path * refactor MsgUpgradeClient * impl update_on_upgrade * upgrade client is permissionless * copy impl to mock comet client component * fill upgrade_validate client component * refactor * impl verify_upgrade * rm unused import * add upgrade_path in client state * support upgrade_path in relayer * rm old code * fix tests * cairo Array fields at the end * use consistent field order * revert to original field order * add tests for Store<Array<T>>::size * refactor new tests * impl StorePacking via ByteArray for Array<u8> and Array<u32> * nits * use Array<u8> store impl for CometConsensusState * rm redundant into-s * fix lint * impl Store for Array<felt252> * impl StorePacking via Serde * export impls * rm redundant store * use Serde StorePacking for Array<Height> and Array<ProofSpec> * use Array<ByteArray> for Store * fix after upgrade_path type change * add missing changes * fix cairo compilation * scaffold protobytes of client and consensus state * fix Array<felt252> unpack * add cometbft dep in ibc-core * serialize comet client and consensus state to protobuf bytes * update comment for mock client
1 parent eb96861 commit 2337161

File tree

26 files changed

+666
-309
lines changed

26 files changed

+666
-309
lines changed

cairo-contracts/Scarb.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ version = "0.1.0"
183183
dependencies = [
184184
"alexandria_data_structures",
185185
"alexandria_numeric",
186+
"cometbft",
186187
"ics23",
187188
"openzeppelin_testing",
188189
"protobuf",
Lines changed: 34 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1+
use cometbft::light_client::ClientState as ProtoCometClientState;
12
use cometbft::utils::Fraction;
23
use core::num::traits::Zero;
3-
use ics23::ProofSpec;
4-
use starknet::SyscallResult;
5-
use starknet::storage_access::{StorageBaseAddress, Store};
4+
use ics23::{ArrayFelt252Store, ProofSpec};
5+
use protobuf::types::message::ProtoCodecImpl;
66
use starknet_ibc_clients::cometbft::CometErrors;
77
use starknet_ibc_core::client::{Duration, Height, HeightPartialOrd, Status, StatusTrait};
88

9+
pub impl ArrayProofSpecStore = ics23::StorePackingViaSerde<Array<ProofSpec>>;
10+
pub impl ArrayByteArrayStore = ics23::StorePackingViaSerde<Array<ByteArray>>;
11+
912
#[derive(Clone, Debug, Drop, PartialEq, Serde, starknet::Store)]
1013
pub struct CometClientState {
1114
pub latest_height: Height,
@@ -16,57 +19,7 @@ pub struct CometClientState {
1619
pub status: Status,
1720
pub chain_id: ByteArray,
1821
pub proof_spec: Array<ProofSpec>,
19-
}
20-
21-
pub impl StoreProofSpecArray of Store<Array<ProofSpec>> {
22-
fn read(address_domain: u32, base: StorageBaseAddress) -> SyscallResult<Array<ProofSpec>> {
23-
Self::read_at_offset(address_domain, base, 0)
24-
}
25-
26-
fn write(
27-
address_domain: u32, base: StorageBaseAddress, value: Array<ProofSpec>,
28-
) -> SyscallResult<()> {
29-
Self::write_at_offset(address_domain, base, 0, value)
30-
}
31-
32-
fn read_at_offset(
33-
address_domain: u32, base: StorageBaseAddress, mut offset: u8,
34-
) -> SyscallResult<Array<ProofSpec>> {
35-
let mut arr: Array<ProofSpec> = array![];
36-
37-
let len: u8 = Store::<u8>::read_at_offset(address_domain, base, offset)
38-
.expect('Storage Span too large');
39-
offset += 1;
40-
41-
let exit = Store::<ProofSpec>::size() * len + offset;
42-
while offset < exit {
43-
let value = Store::<ProofSpec>::read_at_offset(address_domain, base, offset).unwrap();
44-
arr.append(value);
45-
offset += Store::<ProofSpec>::size();
46-
}
47-
48-
Result::Ok(arr)
49-
}
50-
51-
fn write_at_offset(
52-
address_domain: u32, base: StorageBaseAddress, mut offset: u8, mut value: Array<ProofSpec>,
53-
) -> SyscallResult<()> {
54-
let len: u8 = value.len().try_into().expect('Storage - Span too large');
55-
Store::<u8>::write_at_offset(address_domain, base, offset, len).unwrap();
56-
offset += 1;
57-
58-
while let Option::Some(element) = value.pop_front() {
59-
Store::<ProofSpec>::write_at_offset(address_domain, base, offset, element).unwrap();
60-
offset += Store::<ProofSpec>::size();
61-
}
62-
63-
Result::Ok(())
64-
}
65-
66-
// FIXME: Use correct size
67-
fn size() -> u8 {
68-
10 * Store::<ProofSpec>::size()
69-
}
22+
pub upgrade_path: Array<ByteArray>,
7023
}
7124

7225
#[generate_trait]
@@ -109,4 +62,31 @@ pub impl CometClientStateImpl of CometClientStateTrait {
10962

11063
@substitute_client_state == self
11164
}
65+
66+
fn protobuf_bytes(self: CometClientState) -> ByteArray {
67+
let proto_client_state: ProtoCometClientState = self.try_into().unwrap();
68+
ProtoCodecImpl::encode(@proto_client_state)
69+
}
70+
}
71+
72+
pub impl CometClientStateToProto of TryInto<CometClientState, ProtoCometClientState> {
73+
fn try_into(self: CometClientState) -> Option<ProtoCometClientState> {
74+
let frozen_height: Height = self.status.into();
75+
76+
Some(
77+
ProtoCometClientState {
78+
chain_id: self.chain_id,
79+
trust_level: self.trust_level,
80+
trusting_period: self.trusting_period.try_into()?,
81+
unbonding_period: self.unbonding_period.try_into()?,
82+
max_clock_drift: self.max_clock_drift.try_into()?,
83+
frozen_height: frozen_height.into(),
84+
latest_height: self.latest_height.into(),
85+
proof_specs: self.proof_spec,
86+
upgrade_path: self.upgrade_path,
87+
allow_update_after_expiry: false,
88+
allow_update_after_misbehaviour: false,
89+
},
90+
)
91+
}
11292
}

cairo-contracts/packages/clients/src/cometbft/component.cairo

Lines changed: 129 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ pub mod CometClientComponent {
66
use cometbft::verifier::{verify_misbehaviour_header, verify_update_header};
77
use core::num::traits::Zero;
88
use ics23::{
9-
MerkleProof, Proof, array_u8_to_byte_array, byte_array_to_array_u8,
9+
ArrayFelt252Store, MerkleProof, Proof, array_u8_to_byte_array, byte_array_to_array_u8,
1010
verify_membership as ics23_verify_membership,
1111
verify_non_membership as ics23_verify_non_membership,
1212
};
@@ -20,9 +20,8 @@ pub mod CometClientComponent {
2020
use starknet::{ContractAddress, get_block_number, get_block_timestamp, get_caller_address};
2121
use starknet_ibc_clients::cometbft::{
2222
ClientMessage, CometClientState, CometClientStateImpl, CometConsensusState,
23-
CometConsensusStateImpl, CometConsensusStateStore, CometConsensusStateToStore,
24-
CometConsensusStateZero, CometErrors, CometHeader, CometHeaderImpl,
25-
CometHeaderIntoConsensusState, Misbehaviour, MisbehaviourImpl, StoreToCometConsensusState,
23+
CometConsensusStateImpl, CometConsensusStateZero, CometErrors, CometHeader, CometHeaderImpl,
24+
CometHeaderIntoConsensusState, Misbehaviour, MisbehaviourImpl,
2625
};
2726
use starknet_ibc_core::client::{
2827
CreateResponse, CreateResponseImpl, Height, HeightImpl, HeightPartialOrd, HeightZero,
@@ -32,15 +31,17 @@ pub mod CometClientComponent {
3231
UpdateResponse,
3332
};
3433
use starknet_ibc_core::commitment::{StateProof, StateRoot, StateValue};
35-
use starknet_ibc_core::host::ClientIdImpl;
34+
use starknet_ibc_core::host::{
35+
BasePrefix, ClientIdImpl, client_upgrade_path, consensus_upgrade_path,
36+
};
3637
use starknet_ibc_utils::ValidateBasic;
3738

3839
#[storage]
3940
pub struct Storage {
4041
next_client_sequence: u64,
4142
update_heights: Map<u64, Array<Height>>,
4243
client_states: Map<u64, CometClientState>,
43-
consensus_states: Map<(u64, Height), CometConsensusStateStore>,
44+
consensus_states: Map<(u64, Height), CometConsensusState>,
4445
client_processed_times: Map<(u64, Height), u64>,
4546
client_processed_heights: Map<(u64, Height), u64>,
4647
}
@@ -83,7 +84,8 @@ pub mod CometClientComponent {
8384
}
8485

8586
fn upgrade_client(ref self: ComponentState<TContractState>, msg: MsgUpgradeClient) {
86-
self.assert_owner();
87+
self.upgrade_validate(msg.clone());
88+
self.upgrade_execute(msg);
8789
}
8890
}
8991

@@ -190,8 +192,7 @@ pub mod CometClientComponent {
190192
let mut serialized: Array<felt252> = ArrayTrait::new();
191193

192194
let consensus_state: CometConsensusState = self
193-
.read_consensus_state(client_sequence, height)
194-
.into();
195+
.read_consensus_state(client_sequence, height);
195196

196197
consensus_state.serialize(ref serialized);
197198

@@ -363,9 +364,23 @@ pub mod CometClientComponent {
363364
> of UpgradeClientTrait<TContractState> {
364365
fn upgrade_validate(self: @ComponentState<TContractState>, msg: MsgUpgradeClient) {
365366
msg.validate_basic();
367+
368+
self
369+
.verify_upgrade(
370+
msg.client_id.sequence,
371+
msg.upgraded_client_state,
372+
msg.upgraded_consensus_state,
373+
msg.proof_upgrade_client,
374+
msg.proof_upgrade_consensus,
375+
);
366376
}
367377

368-
fn upgrade_execute(ref self: ComponentState<TContractState>, msg: MsgUpgradeClient) {}
378+
fn upgrade_execute(ref self: ComponentState<TContractState>, msg: MsgUpgradeClient) {
379+
self
380+
.update_on_upgrade(
381+
msg.client_id.sequence, msg.upgraded_client_state, msg.upgraded_consensus_state,
382+
);
383+
}
369384
}
370385

371386
// -----------------------------------------------------------
@@ -468,12 +483,82 @@ pub mod CometClientComponent {
468483

469484
fn verify_upgrade(
470485
self: @ComponentState<TContractState>,
486+
client_sequence: u64,
471487
upgrade_client_state: Array<felt252>,
472488
upgrade_consensus_state: Array<felt252>,
473-
proof_upgrade_client: Array<felt252>,
474-
proof_upgrade_consensus: Array<felt252>,
475-
root: ByteArray,
476-
) {}
489+
proof_upgrade_client: StateProof,
490+
proof_upgrade_consensus: StateProof,
491+
) {
492+
let comet_client_state: CometClientState = self.read_client_state(client_sequence);
493+
let latest_height = comet_client_state.latest_height.clone();
494+
495+
let latest_consensus_state = self
496+
.read_consensus_state(client_sequence, comet_client_state.latest_height.clone());
497+
498+
let root = latest_consensus_state.root.clone();
499+
500+
let upgrade_path = comet_client_state.upgrade_path.clone();
501+
502+
let status = self._status(comet_client_state, latest_consensus_state, client_sequence);
503+
504+
assert(status.is_active(), CometErrors::INACTIVE_CLIENT);
505+
506+
assert(
507+
upgrade_path.len() == 1 || upgrade_path.len() == 2,
508+
CometErrors::INVALID_UPGRADE_PATH_LENGTH,
509+
);
510+
511+
let (prefix, upgrade_path) = if upgrade_path.len() == 1 {
512+
("", upgrade_path[0].clone())
513+
} else {
514+
(upgrade_path[0].clone(), upgrade_path[1].clone())
515+
};
516+
517+
let base_prefix = BasePrefix { prefix };
518+
519+
let upgraded_client_path = client_upgrade_path(
520+
base_prefix.clone(), latest_height.revision_height, upgrade_path.clone(),
521+
);
522+
523+
let upgraded_consensus_path = consensus_upgrade_path(
524+
base_prefix, latest_height.revision_height, upgrade_path,
525+
);
526+
527+
let upgraded_client_state = CometClientStateImpl::deserialize(upgrade_client_state);
528+
529+
let upgraded_consensus_state = CometConsensusStateImpl::deserialize(
530+
upgrade_consensus_state,
531+
);
532+
533+
let upgraded_height = upgraded_client_state.latest_height.clone();
534+
535+
assert(upgraded_height > latest_height, CometErrors::INVALID_UPGRADE_HEIGHT);
536+
537+
let upgraded_client_protobuf = StateValue {
538+
value: ics23::byte_array_to_array_u8(@upgraded_client_state.protobuf_bytes()),
539+
};
540+
let upgraded_consensus_protobuf = StateValue {
541+
value: ics23::byte_array_to_array_u8(@upgraded_consensus_state.protobuf_bytes()),
542+
};
543+
544+
self
545+
.verify_membership(
546+
client_sequence,
547+
upgraded_client_path,
548+
upgraded_client_protobuf,
549+
proof_upgrade_client,
550+
root.clone(),
551+
);
552+
553+
self
554+
.verify_membership(
555+
client_sequence,
556+
upgraded_consensus_path,
557+
upgraded_consensus_protobuf,
558+
proof_upgrade_consensus,
559+
root,
560+
);
561+
}
477562
}
478563

479564
#[embeddable_as(CometClientExecution)]
@@ -652,7 +737,32 @@ pub mod CometClientComponent {
652737
client_sequence: u64,
653738
new_client_state: Array<felt252>,
654739
new_consensus_state: Array<felt252>,
655-
) {}
740+
) {
741+
let update_heights = self.read_update_heights(client_sequence);
742+
743+
if update_heights.len() > 0 {
744+
let mut update_heights_span = update_heights.span();
745+
746+
while let Option::Some(height) = update_heights_span.pop_front() {
747+
self.remove_consensus_state(client_sequence, height.clone());
748+
}
749+
750+
self.update_heights.write(client_sequence, array![]);
751+
}
752+
753+
let new_client_state = CometClientStateImpl::deserialize(new_client_state);
754+
let new_consensus_state = CometConsensusStateImpl::deserialize(new_consensus_state);
755+
756+
self
757+
._update_state(
758+
client_sequence,
759+
new_client_state.latest_height,
760+
new_client_state,
761+
new_consensus_state,
762+
get_block_number(),
763+
get_block_timestamp(),
764+
);
765+
}
656766
}
657767

658768
// -----------------------------------------------------------
@@ -880,8 +990,7 @@ pub mod CometClientComponent {
880990
) -> CometConsensusState {
881991
let consensus_state: CometConsensusState = self
882992
.consensus_states
883-
.read((client_sequence, height))
884-
.into();
993+
.read((client_sequence, height));
885994

886995
assert(consensus_state.is_non_zero(), CometErrors::MISSING_CONSENSUS_STATE);
887996

@@ -893,8 +1002,7 @@ pub mod CometClientComponent {
8931002
) -> bool {
8941003
let consensus_state: CometConsensusState = self
8951004
.consensus_states
896-
.read((client_sequence, height))
897-
.into();
1005+
.read((client_sequence, height));
8981006

8991007
consensus_state.is_non_zero()
9001008
}
@@ -1000,7 +1108,7 @@ pub mod CometClientComponent {
10001108
processed_height: u64,
10011109
processed_time: u64,
10021110
) {
1003-
self.consensus_states.write((client_sequence, height.clone()), consensus_state.into());
1111+
self.consensus_states.write((client_sequence, height.clone()), consensus_state);
10041112
self
10051113
.client_processed_heights
10061114
.write((client_sequence, height.clone()), processed_height);
@@ -1011,7 +1119,7 @@ pub mod CometClientComponent {
10111119
ref self: ComponentState<TContractState>, client_sequence: u64, height: Height,
10121120
) {
10131121
let consensus_zero = CometConsensusStateZero::zero();
1014-
self.consensus_states.write((client_sequence, height), consensus_zero.into());
1122+
self.consensus_states.write((client_sequence, height), consensus_zero);
10151123
self.client_processed_times.write((client_sequence, height), 0);
10161124
self.client_processed_heights.write((client_sequence, height), 0);
10171125
}

0 commit comments

Comments
 (0)