From 32f4c345e73518be23173401a6eb552edb334735 Mon Sep 17 00:00:00 2001 From: Jun Kimura Date: Fri, 8 Nov 2024 20:56:31 +0900 Subject: [PATCH 01/11] update verifier crate version Signed-off-by: Jun Kimura --- crates/ibc/Cargo.toml | 6 +-- crates/ibc/src/client_state.rs | 2 +- crates/ibc/src/consensus_state.rs | 30 ++++------- crates/ibc/src/errors.rs | 4 +- crates/ibc/src/header.rs | 83 ++++++++++++++++--------------- crates/ibc/src/misbehaviour.rs | 3 ++ crates/ibc/src/update.rs | 49 +++++++++++------- 7 files changed, 94 insertions(+), 83 deletions(-) diff --git a/crates/ibc/Cargo.toml b/crates/ibc/Cargo.toml index 6edc670..cdff4e6 100644 --- a/crates/ibc/Cargo.toml +++ b/crates/ibc/Cargo.toml @@ -16,10 +16,10 @@ ssz-rs = { git = "https://github.com/bluele/ssz_rs", branch = "serde-no-std", de hex = { version = "0.4.3", default-features = false } ethereum-ibc-proto = { path = "../../proto", default-features = false } -ethereum-consensus = { git = "https://github.com/datachainlab/ethereum-light-client-rs", rev = "v0.1.4", default-features = false } -ethereum-light-client-verifier = { git = "https://github.com/datachainlab/ethereum-light-client-rs", rev = "v0.1.4", default-features = false } +ethereum-consensus = { git = "https://github.com/datachainlab/ethereum-light-client-rs", rev = "6c2b3b0f1a58c4df3eedeb9a36de0e7fcab25007", default-features = false } +ethereum-light-client-verifier = { git = "https://github.com/datachainlab/ethereum-light-client-rs", rev = "6c2b3b0f1a58c4df3eedeb9a36de0e7fcab25007", default-features = false } [dev-dependencies] time = { version = "0.3", default-features = false, features = ["macros", "parsing"] } hex-literal = "0.4.1" -ethereum-light-client-verifier = { git = "https://github.com/datachainlab/ethereum-light-client-rs", rev = "v0.1.4", default-features = false, features = ["test-utils"] } +ethereum-light-client-verifier = { git = "https://github.com/datachainlab/ethereum-light-client-rs", rev = "6c2b3b0f1a58c4df3eedeb9a36de0e7fcab25007", default-features = false, features = ["test-utils"] } diff --git a/crates/ibc/src/client_state.rs b/crates/ibc/src/client_state.rs index fdc1bb8..79370a0 100644 --- a/crates/ibc/src/client_state.rs +++ b/crates/ibc/src/client_state.rs @@ -241,7 +241,7 @@ impl ClientState { } } - fn validate(&self) -> Result<(), Error> { + pub fn validate(&self) -> Result<(), Error> { if self.genesis_validators_root == Root::default() { Err(Error::UninitializedClientStateField( "genesis_validators_root", diff --git a/crates/ibc/src/consensus_state.rs b/crates/ibc/src/consensus_state.rs index b057c78..002ce88 100644 --- a/crates/ibc/src/consensus_state.rs +++ b/crates/ibc/src/consensus_state.rs @@ -224,36 +224,28 @@ impl TrustedConsensusState(&self, ctx: &C) -> SyncCommitteePeriod { - self.state.current_period(ctx) - } } impl LightClientStoreReader for TrustedConsensusState { - fn get_sync_committee( - &self, - ctx: &CC, - period: SyncCommitteePeriod, - ) -> Option> { - let store_period = self.current_period(ctx); - if period == store_period { - self.current_sync_committee.clone() - } else if period == store_period + 1 { - self.next_sync_committee.clone() - } else { - None - } + fn current_period(&self, ctx: &C) -> SyncCommitteePeriod { + self.state.current_period(ctx) + } + + fn current_sync_committee(&self) -> Option> { + self.current_sync_committee.clone() + } + + fn next_sync_committee(&self) -> Option> { + self.next_sync_committee.clone() } fn ensure_relevant_update>( &self, - ctx: &CC, + _ctx: &CC, update: &C, ) -> Result<(), ethereum_light_client_verifier::errors::Error> { - update.ensure_consistent_update_period(ctx)?; if self.state.slot >= update.finalized_beacon_header().slot { Err( ethereum_light_client_verifier::errors::Error::IrrelevantConsensusUpdates( diff --git a/crates/ibc/src/errors.rs b/crates/ibc/src/errors.rs index 78a9e70..1c3ec34 100644 --- a/crates/ibc/src/errors.rs +++ b/crates/ibc/src/errors.rs @@ -40,8 +40,8 @@ pub enum Error { AccountStorageRootMismatch(H256, H256, H256, String, Vec), /// invalid account storage root: {0:?} InvalidAccountStorageRoot(Vec), - /// future period error: store={0} update={1} - FuturePeriodError(U64, U64), + /// store does not support the finalized_period: store_period={0} finalized_period={1} + StoreNotSupportedFinalizedPeriod(U64, U64), /// both updates of misbehaviour data must have same period: {0} != {1} DifferentPeriodInNextSyncCommitteeMisbehaviour(SyncCommitteePeriod, SyncCommitteePeriod), /// both updates of misbehaviour data must have next sync committee diff --git a/crates/ibc/src/header.rs b/crates/ibc/src/header.rs index 4befa36..3700275 100644 --- a/crates/ibc/src/header.rs +++ b/crates/ibc/src/header.rs @@ -197,47 +197,50 @@ mod tests { let dummy_execution_state_root = [1u8; 32].into(); let dummy_execution_block_number = 1; - let update = gen_light_client_update_with_params::<32, _>( - &ctx, - base_signature_slot, - base_attested_slot, - base_finalized_epoch, - dummy_execution_state_root, - dummy_execution_block_number.into(), - current_sync_committee, - scm.get_committee(2), - 32, - ); - let update = to_consensus_update_info(update); - let header = Header { - trusted_sync_committee: TrustedSyncCommittee { - height: ibc::Height::new(1, 1).unwrap(), - sync_committee: current_sync_committee.to_committee().clone(), - is_next: true, - }, - consensus_update: update.clone(), - execution_update: ExecutionUpdateInfo::default(), - account_update: AccountUpdateInfo::default(), - timestamp: Timestamp::from_nanoseconds(1730729158 * 1_000_000_000).unwrap(), - }; - let any = IBCAny::from(header.clone()); - let decoded = Header::<32>::try_from(any).unwrap(); - assert_eq!(header, decoded); + for b in [false, true] { + let update = gen_light_client_update_with_params::<32, _>( + &ctx, + base_signature_slot, + base_attested_slot, + base_finalized_epoch, + dummy_execution_state_root, + dummy_execution_block_number.into(), + current_sync_committee, + scm.get_committee(2), + b, + 32, + ); + let update = to_consensus_update_info(update); + let header = Header { + trusted_sync_committee: TrustedSyncCommittee { + height: ibc::Height::new(1, 1).unwrap(), + sync_committee: current_sync_committee.to_committee().clone(), + is_next: true, + }, + consensus_update: update.clone(), + execution_update: ExecutionUpdateInfo::default(), + account_update: AccountUpdateInfo::default(), + timestamp: Timestamp::from_nanoseconds(1730729158 * 1_000_000_000).unwrap(), + }; + let any = IBCAny::from(header.clone()); + let decoded = Header::<32>::try_from(any).unwrap(); + assert_eq!(header, decoded); - let header = Header { - trusted_sync_committee: TrustedSyncCommittee { - height: ibc::Height::new(1, 1).unwrap(), - sync_committee: current_sync_committee.to_committee().clone(), - is_next: true, - }, - consensus_update: update, - execution_update: ExecutionUpdateInfo::default(), - account_update: AccountUpdateInfo::default(), - timestamp: Timestamp::from_nanoseconds(0).unwrap(), - }; - let any = IBCAny::from(header.clone()); - let res = Header::<32>::try_from(any); - assert!(res.is_err()); + let header = Header { + trusted_sync_committee: TrustedSyncCommittee { + height: ibc::Height::new(1, 1).unwrap(), + sync_committee: current_sync_committee.to_committee().clone(), + is_next: true, + }, + consensus_update: update, + execution_update: ExecutionUpdateInfo::default(), + account_update: AccountUpdateInfo::default(), + timestamp: Timestamp::from_nanoseconds(0).unwrap(), + }; + let any = IBCAny::from(header.clone()); + let res = Header::<32>::try_from(any); + assert!(res.is_err(), "header with zero timestamp should fail"); + } } fn to_consensus_update_info( diff --git a/crates/ibc/src/misbehaviour.rs b/crates/ibc/src/misbehaviour.rs index ff620e7..4929080 100644 --- a/crates/ibc/src/misbehaviour.rs +++ b/crates/ibc/src/misbehaviour.rs @@ -244,6 +244,7 @@ mod tests { dummy_execution_block_number.into(), current_sync_committee, scm.get_committee(2), + true, 32, ); let update_2 = gen_light_client_update_with_params::<32, _>( @@ -255,6 +256,7 @@ mod tests { dummy_execution_block_number.into(), current_sync_committee, scm.get_committee(3), + true, 32, ); let update_1 = to_consensus_update_info(update_1); @@ -285,6 +287,7 @@ mod tests { dummy_execution_block_number.into(), current_sync_committee, scm.get_committee(2), + true, 32, ); diff --git a/crates/ibc/src/update.rs b/crates/ibc/src/update.rs index 9744209..9c8a064 100644 --- a/crates/ibc/src/update.rs +++ b/crates/ibc/src/update.rs @@ -10,6 +10,12 @@ use ethereum_consensus::{ }; use ibc::timestamp::Timestamp; +/// Apply the verified updates to the state and return the new state. +/// +/// CONTRACT: `apply_updates` must be called after `SyncProtocolVerifier::validate_updates()` +/// The `update` satisfies the following conditions: +/// - finalized_period <= attested_period <= signature_period +/// - `consensus_update`'s signature period in (store_period, store_period + 1) == True pub fn apply_updates( ctx: &C, client_state: &ClientState, @@ -20,10 +26,11 @@ pub fn apply_updates( timestamp: Timestamp, ) -> Result<(ClientState, ConsensusState), Error> { let store_period = consensus_state.current_period(ctx); - let update_slot = consensus_update.finalized_header.0.slot; - let update_period = compute_sync_committee_period_at_slot(ctx, update_slot); + let update_finalized_slot = consensus_update.finalized_header.0.slot; + let update_finalized_period = compute_sync_committee_period_at_slot(ctx, update_finalized_slot); let timestamp = timestamp.into_tm_time().unwrap().unix_timestamp() as u64; - let finalized_header_timestamp: u64 = compute_timestamp_at_slot(ctx, update_slot).into(); + let finalized_header_timestamp: u64 = + compute_timestamp_at_slot(ctx, update_finalized_slot).into(); if finalized_header_timestamp != timestamp { return Err(Error::UnexpectedTimestamp( finalized_header_timestamp, @@ -31,27 +38,28 @@ pub fn apply_updates( )); } - // We can assume that the update's finalized period is equal to the attested period by `LightClientStoreReader::ensure_relevant_update()` - // The sync committee info is based on the attested period, so we can use the finalized period to determine the sync committee info. // Let `store_period` be the period of the current sync committe of the consensus state, then the state transition is the following: - // - If `store_period == update_period`, then the new consensus state will have the same sync committee info as the current consensus state. - // - If `store_period + 1 == update_period`, then the new consensus state will have the current sync committee as the next sync committee of the current consensus state, + // - If `store_period == update_finalized_period`, then the new consensus state will have the same sync committee info as the current consensus state. + // - If `store_period + 1 == update_finalized_period`, then the new consensus state will have the current sync committee as the next sync committee of the current consensus state, // and the next sync committee of the new consensus state will be the next sync committee of the update. - - let new_consensus_state = if store_period == update_period { + let new_consensus_state = if store_period == update_finalized_period { + // store_period == finalized_period <= attested_period <= signature_period ConsensusState { - slot: update_slot, + slot: update_finalized_slot, storage_root: account_storage_root.0.to_vec().into(), - timestamp: wrap_compute_timestamp_at_slot(ctx, update_slot)?, + timestamp: wrap_compute_timestamp_at_slot(ctx, update_finalized_slot)?, current_sync_committee: consensus_state.current_sync_committee.clone(), next_sync_committee: consensus_state.next_sync_committee.clone(), } - } else if store_period + 1 == update_period { + } else if store_period + 1 == update_finalized_period { + // store_period + 1 == finalized_period == attested_period == signature_period + // Why `finalized_period == attested_period == signature_period` here? + // Because our store only have the current or next sync committee info, so the verified update's signature period must match the `store_period + 1` here. if let Some((update_next_sync_committee, _)) = consensus_update.next_sync_committee { ConsensusState { - slot: update_slot, + slot: update_finalized_slot, storage_root: account_storage_root.0.to_vec().into(), - timestamp: wrap_compute_timestamp_at_slot(ctx, update_slot)?, + timestamp: wrap_compute_timestamp_at_slot(ctx, update_finalized_slot)?, current_sync_committee: consensus_state.next_sync_committee.clone(), next_sync_committee: update_next_sync_committee.aggregate_pubkey, } @@ -59,19 +67,24 @@ pub fn apply_updates( // Relayers must submit an update that contains the next sync committee if the update period is `store_period + 1`. return Err(Error::NoNextSyncCommitteeInConsensusUpdate( store_period.into(), - update_period.into(), + update_finalized_period.into(), )); } } else { - // store_period + 1 < update_period - // Relayers must submit an update that contains the next sync committee if the update period is `store_period + 1`` in advance. - return Err(Error::FuturePeriodError(store_period, update_period)); + // store_period + 1 < update_finalized_period or store_period > update_finalized_period + // The store(=consensus state) cannot apply such updates here because the current or next sync committee corresponding to the `finalized_period` is unknown. + return Err(Error::StoreNotSupportedFinalizedPeriod( + store_period, + update_finalized_period, + )); }; let mut new_client_state = client_state.clone(); if client_state.latest_execution_block_number < block_number { new_client_state.latest_execution_block_number = block_number; } + new_client_state.validate()?; + new_consensus_state.validate()?; Ok((new_client_state, new_consensus_state)) } From 1e81ef7ad9a6fe2eb2b8be2889020771b8906e4d Mon Sep 17 00:00:00 2001 From: Jun Kimura Date: Sun, 10 Nov 2024 22:52:45 +0900 Subject: [PATCH 02/11] clarify not supported functions Signed-off-by: Jun Kimura --- crates/ibc/src/client_state.rs | 70 +++++++++++++++++----------------- 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/crates/ibc/src/client_state.rs b/crates/ibc/src/client_state.rs index 79370a0..156c7a6 100644 --- a/crates/ibc/src/client_state.rs +++ b/crates/ibc/src/client_state.rs @@ -279,10 +279,6 @@ impl ClientState { } impl Ics2ClientState for ClientState { - fn chain_id(&self) -> ChainId { - todo!() - } - fn client_type(&self) -> ClientType { eth_client_type() } @@ -295,15 +291,6 @@ impl Ics2ClientState for ClientState bool { - todo!() - } - - fn zero_custom_fields(&mut self) { - todo!() - } - fn initialise( &self, consensus_state: Any, @@ -446,27 +433,6 @@ impl Ics2ClientState for ClientState Result<(), ClientError> { - todo!() - } - - #[allow(unused_variables)] - fn update_state_with_upgrade_client( - &self, - upgraded_client_state: Any, - upgraded_consensus_state: Any, - ) -> Result { - todo!() - } - fn verify_client_consensus_state( &self, proof_height: ibc::Height, @@ -653,6 +619,42 @@ impl Ics2ClientState for ClientState ChainId { + unimplemented!() + } + + #[allow(unused_variables)] + fn expired(&self, elapsed: Duration) -> bool { + unimplemented!() + } + + fn zero_custom_fields(&mut self) { + unimplemented!() + } + + #[allow(unused_variables)] + fn verify_upgrade_client( + &self, + upgraded_client_state: Any, + upgraded_consensus_state: Any, + proof_upgrade_client: ibc_proto::ibc::core::commitment::v1::MerkleProof, + proof_upgrade_consensus_state: ibc_proto::ibc::core::commitment::v1::MerkleProof, + root: &ibc::core::ics23_commitment::commitment::CommitmentRoot, + ) -> Result<(), ClientError> { + unimplemented!() + } + + #[allow(unused_variables)] + fn update_state_with_upgrade_client( + &self, + upgraded_client_state: Any, + upgraded_consensus_state: Any, + ) -> Result { + unimplemented!() + } } fn validate_state_timestamp_within_trusting_period( From 70fa87380b06d19d1c524e47d99b9180f7219457 Mon Sep 17 00:00:00 2001 From: Jun Kimura Date: Sun, 10 Nov 2024 23:31:49 +0900 Subject: [PATCH 03/11] fix `verify_account_storage`'s signature Signed-off-by: Jun Kimura --- crates/ibc/src/client_state.rs | 50 ++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/crates/ibc/src/client_state.rs b/crates/ibc/src/client_state.rs index 156c7a6..a2d560d 100644 --- a/crates/ibc/src/client_state.rs +++ b/crates/ibc/src/client_state.rs @@ -103,17 +103,20 @@ impl ClientState { pub fn verify_account_storage( &self, state_root: H256, - address: &Address, account_update: &AccountUpdateInfo, ) -> Result<(), Error> { match self .execution_verifier - .verify_account(state_root, address, account_update.account_proof.clone()) + .verify_account( + state_root, + &self.ibc_address, + account_update.account_proof.clone(), + ) .map_err(|e| { Error::MPTVerificationError( e, state_root, - hex::encode(address.0), + hex::encode(self.ibc_address.0), account_update .account_proof .iter() @@ -129,7 +132,7 @@ impl ClientState { account_update.account_storage_root, account.storage_root, state_root, - hex::encode(address.0), + hex::encode(self.ibc_address.0), account_update .account_proof .iter() @@ -146,7 +149,7 @@ impl ClientState { account_update.account_storage_root, H256::default(), state_root, - hex::encode(address.0), + hex::encode(self.ibc_address.0), account_update .account_proof .iter() @@ -343,11 +346,7 @@ impl Ics2ClientState for ClientState::default(); + let client_state = + ClientState::<{ ethereum_consensus::preset::minimal::PRESET.SYNC_COMMITTEE_SIZE }> { + ibc_address: Address(hex!("ff77D90D6aA12db33d3Ba50A34fB25401f6e4c4F")), + ..Default::default() + }; let account_proof = decode_eip1184_rlp_proof( hex!("f901fff90191a05844e303fa8db3fa31c729db25d9b593367f853b4cbcb1a91fc85eda11e16617a09bb111cd80eee4c6ae6af0d01422ae82fccfa80d0267c4c8d525bc7f2b6233afa0323230228b1ba9b7eb88084b6d1ed9b75813a2da2d5ff0df9067335f5f55444ca0bfca1461a76f96944aa00afff03dc8de770275fbbe360f6ee03b0fe0ce902fd8a04c7579812e09de2b1aa746b0a047d357e898e9d634ac185d7e9d25b3d2336ab3808080a0c7de43d788c5228ebde29b62cb0f9b9eb10c0cb9b1078d6a51f768e0cdf296d6a0b8ad2523a3d1fdf33b627f598622775508297710e3623de115f2174c7f5727dfa023910890abfb016861bb7916cb555add80e552f118c0f1b93ec7d26798976f1da077153f3a45bebfb8b6709bd52e71d0993e9ecfd4e425204e258e5e5ac775ee73a01b42efb18b5af3defc59ba21f68965c5a28c716e109df937d216a2041fee4770a06b4b8f8ad0ae7588581c191bf177d5020fcc0f9152123cd26b3acf4e3469744280a0b4ec201ec80c64cefbe351f2febea48eb21c0d65d3e1c868178ece65e3d63ff480f869a0346090ccaa6fa9fa12360268a84aaba21af051a53bfdc84493350c840f61b79eb846f8440180a0d70e9391a3dd508a60195d2a5e12fb2f7e49582f9ce2c12477299377ccfadaada073092abb9be4a3fa206fd43699af07ff9d4278c27693f013fceb7780f3654c09").to_vec() ).unwrap(); @@ -947,7 +948,6 @@ mod tests { H256(hex!( "48b7747ba1094684d9197bbaa5dcb134587d23e493fb53a29e400c50e50f5147" )), - &Address(hex!("ff77D90D6aA12db33d3Ba50A34fB25401f6e4c4F")), &AccountUpdateInfo { account_proof, account_storage_root: H256(hex!( @@ -958,6 +958,28 @@ mod tests { assert!(res.is_ok(), "{:?}", res); } + #[test] + fn test_verify_account_storage_non_existence() { + let client_state = + ClientState::<{ ethereum_consensus::preset::minimal::PRESET.SYNC_COMMITTEE_SIZE }> { + ibc_address: Address(hex!("a7f733a4fEA1071f58114b203F57444969b86524")), + ..Default::default() + }; + let account_proof = decode_eip1184_rlp_proof( + hex!("f90c8bf90211a0735c089329da81ce7b2d42666be5a1937cba65e1e018ca007d2d89097643093da050ac94d9d3f41b5affc198a6fe058bbf0b5c325da0bc89914e94cd792c835ec7a0dd6fafef2a8250c254db15fa44c2d53dc543eddb86800bcd892bd98c0adb2fafa0603d4baa1bf91623b90e4d7760123036058fabdc5895f1efab03d9bc9b92da9ea0688c8a3f57cf3579a8b19011d9a811e5bcbcd467216935fdfde60884e6af7991a0812a0308609cc2b529630ab727abd3b1aa974896e4cd0a02bbe517f77b142a2ba02904a77c6e9c680d4d302e5a51b847fca63a6532b21b07dd41a3ac49f4151349a0e179454a28109b5aab7b880693b5786b568781d9aa45986f74be7d6a7a818d72a07f3363c3908198f0aaa22c64185be10461a51524bc2b53f5d622caef25c672a0a0e65b893234155029bcf2f893bfdf4498509062c0949eb0ffc027c075267b999fa01d6a1e0b72b63a7a00083a40572d4041116a286fa3a2d747b25fa0cefe07642ca0d8b29fe0f182ddf0482f5b3ab6b3a0640e63177a78eadf75f5773ed534214e4aa0de1d7bd5c5dbc04a2f734621a7225856c722cce92fe201cab1348282b05a1d97a0f9e111c8222ff2bc2fe565ff7f626c9748239c2038e288380081eb2a3af0c908a07dd132ff024cf49ed3f49149f8bae764e9a173f499ccfcd9f408dd2000bb531da0b954951fd86e5b275c759fbd29eaa292f3c84fdbb6bcc5b1e714a1d838582aae80f90211a04d152a07d2c9c1547501953755563814d7cb184a7756551eaf482b759dae5769a07316575d385637101fdb5f52e676e10f25ae0aa08eee125a7e5bedf4aad6c122a011e2f69401bab09a648aff06e1893c93e2e58a9635d0bcbeb56ae3f585a04b85a086e49bb50598013f14675177ae79c1da54e6c2bbaf91c3b1341142c29271c409a0ceff5f5bd8e1824b24651f15e4ba56fee26cb0b9173f7d4ebab3e0293c7269e7a0fecedf695e9918f9c3397accb5416c4696999fa64fa2e842ec79be8cb93a8651a0854ca3709cc3460ada39aea9619d7f571c117b6054ecb2c4370584d63cf03641a0826eff5d12e4895656e42b47bb3ae6d82027b210c09c85ea702c117193beab3da009fe1cc705eb0d3ac788027f618c076ef1510aadcee6cd736ac931c763f9ef67a0cafc24169ce2d0bc3a14471e1d74470cf692df45e672a4b5281be6634d0f06f4a06dd0a6ae12c583185056c34a27b4df3e66b7da685d90b05e05f73e4904dd502aa0a43f00480c0a7219aca6e9681573abe0206a0ac744fddbd721a00d9342bbc418a0f97f13efdb911e697b75663c41dca7e5b31ad93a6d460bac224e30c1188fb7e3a07fc2f215ae82e3c19b48ec6f1a28427f17a04668308f573c20b68626bc85955ca04896f54096f0489f5d0649b4d7fc796caccfbb275dfa1a90b8b9be2116a7e273a0cdbca11f9ed5ea1d347dd6f40c8bdb8505f21deb53161ea57ebd08fe0becfa8f80f90211a03bf4c6b5933c499c155a6edb44b781dc408992c2c4c16ca1d75ad19e23fafeefa0b55c56b13f5d19dde3d7d96cc156a92d2feb78d29125f9bd2925202f466313a7a0244e4254c915f0209716c58e02b1e59702b3b9a28c5c59d361dd06cd91ec70f9a06ea7d219827e2a71031d36d892fffb6aca878ec90f0e1086cbf6a5dd1ff8763ba0751e39cd8c27f3607a42d11897cd190d0984be5847cbe6100751d06d04c637bba002871c10a84f539119d02559afcc35d6751864529189ce5c7038a2c118601fe3a0306ff9c515871b0080b9334022351644dbe44901c2b2f267311675829c1307fca0a963d8f5da27142226b9607bd09d1aa7d64f3615236bc22cb7439045b0d93abea0e91fb125186252297f099d76d4f40ee948d5a0fb2b469ee63c685db204be7a4ca0ccd4667345dd458431de1d9fab425f36553d444e9b059d847d72b4a193971f1aa0d3b2c96703267e6040450109d4226f4b05cd4274c5f4ed99aa97b83b70044bf1a0543e2abdea1838ca8408ac52fb1d7fb170b2e822aa6989558d4fbf47a0f8b851a016b137139bdb068d710119ec01df37f40e6c6db0c31346bf083893a73e3402e0a070ef51e27ac486580d225c4f7bb73f6456f2fbc69ea5e945471d9c86268d3da4a0838f376bf27fbe43dcede5396b787b4047ab9ab13c3d54b71ea7ded3aeb44ef1a02c207350ede911a939db2cbde3644c5508592fe281d97b7f798753779ae6651a80f90211a04081815e12cf03a52f183def5e687076094b0bc6387363c051b3e4b1be4d1d19a06c73c48b7349672f346b03a3df4838129ed5f92eddddd7e2d1e2efad591602cba0b78231ff87a3f6b239661cde00ea1f605e78a70886467d2fba5ec455327182a4a00e844f2238c3a9a7401aacd5382fd8e9ade8c5d9a2ed18936f18b7a4a0ce0159a0f0cf87ad75cbe0d422d501ceecd3eeb2322e987dd06b948719296214d2b1bd3da0b972c5937a9fc152bc6fa930e84060e2bef4a63d83978a4ec6bc14b60bf452efa0db85e78720006b5ea6e7b258522837815cfb16a3c634e74913394239fb83397ea0f79388ee57552bb0f2cd6fb7fcb716156607be0f95acdab409b64f590c7d7f72a0140623eb8411a98ac14271b6838908e6a27803bfea3073aef8c8825eaed50fe1a09400f4a94faf9a7a80d58f889ca08a665c82e1cac2039a7fcc0ae9698621f1e0a0ebb02920f9288829afb95192e7466da33e45f996d9cd29d7fc06cad275663d47a0dfd361a59760d8542085eb26b3108ccda0111407637599dfd6ee1db3f8d7829ea07bfd10a32a6183ace7f87df01d5dc24bf0e9d09f65e7954096ab6386ac72bf3fa056a0b81e9ddedb42072c3b9100cf911c1933d16e231dde74647d33b7e8de95b7a0ed1a49b7ed1daea815c0cb7400776af17dbc4b27300cffc5c5d61ce01305f6c6a0b98bf6e4aadc8c9f754d04107972e868325c1c0b65338b14e80a8754952c533780f90211a06c58a57085037dfc8db7fcc57396521078f8fcfb9e76eeb2b45d3408c1fdd191a056dcc694badf8487675150d3f90d7452e1d008da178140a84cab3708e94eb4eda06ee4844656a92e04a04a5469280c9d69b4f12ab8f29896b2645fcb92603bea80a0807c0b0f7cdfa86c4390d62202ad4eacfe0a6182189b34bad027db16336c5a78a02b1b7221d0a98161170a5008a702030f8ec84d02f939b59755952ce3437bba0aa01c5e574e042f54cf46caec858db5ca34901a000195d1f044e03dcbb66f904e36a0d0f3ab90005c9f49578bbba90bca526e4034d86d49fde2c8269be58a30f540e3a0145539ee81a9fb21e1af7b5851d57a497765dd5c4351904c8a725ec755e0ce52a0e90a6fd2863da09c2bf1f1a4f0d8f04a095bdc76816000aeaef5bb20e4f717d9a033d95a81d8b601f9dbdbea947a7052eb849dd91875e734da645c9f240418c72fa053dc0376d2c1309c1bbe7755d7ede7bfa4af13c2c933538a539c75bdecbd9e53a0707665486d4924ae7eecfcf41f7f804906140baecf43fa72ec8419bcdfd48f1ba0ccf9006732611705137f5562d6b59442af44c13ea97df11c09df468dbe0a7323a0385c7d72bf637b580a2ddec76007d12d1048a2553ec662d96ed25ccedd97053fa0939aa354a3598e3248baf25e27953c3812b2c555d5e43db59d330ab6f9e8c3c3a0c941eff5b200af34d12362bf73856fa0164d788274804245239e66c55091135080f901b18080a0a3c8aca2eeb300dc458c7fc99aadba23e3bbd0f88e9def4c89c8c58c5e4b468aa02440a82db7c8b7044dd2fba70e5204e481032a630173d8d9752851e7df7e4240a0a60accaa78c07a21cfb1414797e9d8dd1236cf96bb1bb56235ac899906c25bc7a047e97e35f2cd979e6b93001f40002e22a0e754923d6e093c0f7cf2578d460e82a017e89cef18477aa7708df31998c60d1acd734e0266e70d8388914ab6f53d7ce9a000031279827d634f41754800f88bc0e9e134cb2d1d66065493c6ef32f22ed1c6a03bdc555af1c06c2c53d99b142ef0fc72c4b58bbfe63975c1aeda24a3eb2ea66ca06408ef004b300be9baccefd12ee648e24adc85841af0d0037c56c3992a0a5e2d80a0b4c8ba93a4166cfc2a051400b8afcdb41d5c74be9d105fb39613f8d9ab064a4da073f59f57bc2e6297832cb37f6618ba99279e2f32d2661110c1682ed2e8694c7ca056ec3e008e136bdf75ee10371aad81c93e6592dc5224b0b3018fea73bae89f7da01cf891d6aa49256fbe12d75e60adfbc612826164564eb89c19371a2625fb8633a04b147c2c876e2578176a1789f8c80d2c4674fbc6ea1cdd319e4b89085594fc4880f87180808080a0898c7859ae8ec9411296d9568545abbe3395dcab69264d16de52cf2489bea53e808080808080a0fdbf45b5370653412069c67c697029941cc4c34a563d265b2b34f95656cb2a38a06a47e9ff626afe400b0591b2632976db76ff66355e000d89b39fa40c3148934f80808080").to_vec() + ).unwrap(); + let res = client_state.verify_account_storage( + H256(hex!( + "568a51c3253bbd2d46e3923b35df0489712df11453fd04dd71341120356952c0" + )), + &AccountUpdateInfo { + account_proof, + account_storage_root: H256::default(), // non-existence + }, + ); + assert!(res.is_ok(), "{:?}", res); + } + #[test] fn test_trusting_period_validation() { { From 4bfad4883fade4f31b2be582a3a9e3f72a903539 Mon Sep 17 00:00:00 2001 From: Jun Kimura Date: Sun, 10 Nov 2024 23:59:41 +0900 Subject: [PATCH 04/11] add validation for supported minimum fork version Signed-off-by: Jun Kimura --- crates/ibc/src/client_state.rs | 63 +++++++++++++++++++++++++++++++++- crates/ibc/src/errors.rs | 2 ++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/crates/ibc/src/client_state.rs b/crates/ibc/src/client_state.rs index a2d560d..3b40831 100644 --- a/crates/ibc/src/client_state.rs +++ b/crates/ibc/src/client_state.rs @@ -8,7 +8,7 @@ use crate::update::apply_updates; use crate::{eth_client_type, internal_prelude::*}; use core::time::Duration; use ethereum_consensus::beacon::{Epoch, Root, Slot, Version}; -use ethereum_consensus::fork::{ForkParameter, ForkParameters, ForkSpec}; +use ethereum_consensus::fork::{ForkParameter, ForkParameters, ForkSpec, BELLATRIX_INDEX}; use ethereum_consensus::types::{Address, H256, U64}; use ethereum_ibc_proto::ibc::lightclients::ethereum::v1::{ ClientState as RawClientState, Fork as RawFork, ForkSpec as RawForkSpec, @@ -257,6 +257,8 @@ impl ClientState { Err(Error::UninitializedClientStateField("genesis_time")) } else if self.fork_parameters == ForkParameters::default() { Err(Error::UninitializedClientStateField("fork_parameters")) + } else if self.fork_parameters.forks().len() <= BELLATRIX_INDEX { + Err(Error::MissingBellatrixFork) } else if self.seconds_per_slot == U64::default() { Err(Error::UninitializedClientStateField("seconds_per_slot")) } else if self.slots_per_epoch == Slot::default() { @@ -931,8 +933,67 @@ fn trim_left_zero(value: &[u8]) -> &[u8] { #[cfg(test)] mod tests { use super::*; + use ethereum_consensus::fork::{ + altair::ALTAIR_FORK_SPEC, bellatrix::BELLATRIX_FORK_SPEC, capella::CAPELLA_FORK_SPEC, + deneb::DENEB_FORK_SPEC, + }; + use ethereum_consensus::preset::minimal::PRESET; use hex_literal::hex; use time::{macros::datetime, OffsetDateTime}; + use tiny_keccak::{Hasher, Keccak}; + + fn keccak256(s: &str) -> H256 { + let mut hasher = Keccak::v256(); + let mut output = [0u8; 32]; + hasher.update(s.as_bytes()); + hasher.finalize(&mut output); + H256::from_slice(&output) + } + + #[test] + fn test_client_state_conversion() { + let mut client_state = + ClientState::<{ ethereum_consensus::preset::minimal::PRESET.SYNC_COMMITTEE_SIZE }> { + genesis_validators_root: keccak256("genesis_validators_root"), + min_sync_committee_participants: 1.into(), + genesis_time: 1.into(), + fork_parameters: ForkParameters::new( + Version([0, 0, 0, 1]), + vec![ + ForkParameter::new(Version([1, 0, 0, 1]), U64(0), ALTAIR_FORK_SPEC), + ForkParameter::new(Version([2, 0, 0, 1]), U64(0), BELLATRIX_FORK_SPEC), + ForkParameter::new(Version([3, 0, 0, 1]), U64(0), CAPELLA_FORK_SPEC), + ForkParameter::new(Version([4, 0, 0, 1]), U64(0), DENEB_FORK_SPEC), + ], + ) + .unwrap(), + seconds_per_slot: PRESET.SECONDS_PER_SLOT, + slots_per_epoch: PRESET.SLOTS_PER_EPOCH, + epochs_per_sync_committee_period: PRESET.EPOCHS_PER_SYNC_COMMITTEE_PERIOD, + ibc_address: Address(hex!("ff77D90D6aA12db33d3Ba50A34fB25401f6e4c4F")), + ibc_commitments_slot: keccak256("ibc_commitments_slot"), + trust_level: Fraction::new(2, 3), + trusting_period: Duration::from_secs(60 * 60 * 27), + max_clock_drift: Duration::from_secs(60), + latest_execution_block_number: 1.into(), + frozen_height: None, + consensus_verifier: Default::default(), + execution_verifier: Default::default(), + }; + let res = client_state.validate(); + assert!(res.is_ok(), "{:?}", res); + client_state.fork_parameters = ForkParameters::new( + Version([0, 0, 0, 1]), + vec![ForkParameter::new( + Version([1, 0, 0, 1]), + U64(0), + ALTAIR_FORK_SPEC, + )], + ) + .unwrap(); + let res = client_state.validate(); + assert!(res.is_err(), "{:?}", res); + } #[test] fn test_verify_account_storage() { diff --git a/crates/ibc/src/errors.rs b/crates/ibc/src/errors.rs index 1c3ec34..95c8c61 100644 --- a/crates/ibc/src/errors.rs +++ b/crates/ibc/src/errors.rs @@ -78,6 +78,8 @@ pub enum Error { UninitializedClientStateField(&'static str), /// uninitialized consensus state field: {0} UninitializedConsensusStateField(&'static str), + /// missing bellatrix fork + MissingBellatrixFork, /// client frozen: frozen_height={frozen_height} target_height={target_height} ClientFrozen { frozen_height: Height, From f25c496b1a37ba96d9bd63a1c11dd522db223bed Mon Sep 17 00:00:00 2001 From: Jun Kimura Date: Mon, 11 Nov 2024 09:22:08 +0900 Subject: [PATCH 05/11] add type conversion tests Signed-off-by: Jun Kimura --- crates/ibc/src/client_state.rs | 27 ++++++++++++++++---------- crates/ibc/src/consensus_state.rs | 32 +++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 10 deletions(-) diff --git a/crates/ibc/src/client_state.rs b/crates/ibc/src/client_state.rs index 3b40831..77b7175 100644 --- a/crates/ibc/src/client_state.rs +++ b/crates/ibc/src/client_state.rs @@ -940,19 +940,10 @@ mod tests { use ethereum_consensus::preset::minimal::PRESET; use hex_literal::hex; use time::{macros::datetime, OffsetDateTime}; - use tiny_keccak::{Hasher, Keccak}; - - fn keccak256(s: &str) -> H256 { - let mut hasher = Keccak::v256(); - let mut output = [0u8; 32]; - hasher.update(s.as_bytes()); - hasher.finalize(&mut output); - H256::from_slice(&output) - } #[test] fn test_client_state_conversion() { - let mut client_state = + let client_state = ClientState::<{ ethereum_consensus::preset::minimal::PRESET.SYNC_COMMITTEE_SIZE }> { genesis_validators_root: keccak256("genesis_validators_root"), min_sync_committee_participants: 1.into(), @@ -982,6 +973,13 @@ mod tests { }; let res = client_state.validate(); assert!(res.is_ok(), "{:?}", res); + + let any_client_state: Any = client_state.clone().into(); + let client_state2 = ClientState::try_from(any_client_state).unwrap(); + assert_eq!(client_state, client_state2); + + // Unexpected fork parameters + let mut client_state = client_state.clone(); client_state.fork_parameters = ForkParameters::new( Version([0, 0, 0, 1]), vec![ForkParameter::new( @@ -1176,4 +1174,13 @@ mod tests { assert!(trim_left_zero(&[0, 0, 0, 0]).is_empty()); assert!(trim_left_zero(&[]).is_empty()); } + + fn keccak256(s: &str) -> H256 { + use tiny_keccak::{Hasher, Keccak}; + let mut hasher = Keccak::v256(); + let mut output = [0u8; 32]; + hasher.update(s.as_bytes()); + hasher.finalize(&mut output); + H256::from_slice(&output) + } } diff --git a/crates/ibc/src/consensus_state.rs b/crates/ibc/src/consensus_state.rs index 002ce88..c5b04dc 100644 --- a/crates/ibc/src/consensus_state.rs +++ b/crates/ibc/src/consensus_state.rs @@ -34,8 +34,10 @@ pub struct ConsensusState { /// timestamp from execution payload pub timestamp: Timestamp, /// aggregate public key of current sync committee + /// "current" indicates a period corresponding to the `slot` pub current_sync_committee: PublicKey, /// aggregate public key of next sync committee + /// "next" indicates `current + 1` period pub next_sync_committee: PublicKey, } @@ -269,8 +271,29 @@ impl From H256 { + use tiny_keccak::{Hasher, Keccak}; + let mut hasher = Keccak::v256(); + let mut output = [0u8; 32]; + hasher.update(s.as_bytes()); + hasher.finalize(&mut output); + H256::from_slice(&output) + } } From ffe5a51f8f0b812df1b73c5f201d10927cac145f Mon Sep 17 00:00:00 2001 From: Jun Kimura Date: Mon, 11 Nov 2024 10:25:55 +0900 Subject: [PATCH 06/11] add test for `TrustedConsensusState` Signed-off-by: Jun Kimura --- crates/ibc/src/consensus_state.rs | 51 +++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/crates/ibc/src/consensus_state.rs b/crates/ibc/src/consensus_state.rs index c5b04dc..08162d9 100644 --- a/crates/ibc/src/consensus_state.rs +++ b/crates/ibc/src/consensus_state.rs @@ -272,6 +272,7 @@ impl From::new(1, 2); + let current_sync_committee = scm.get_committee(1); + let next_sync_committee = scm.get_committee(2); + + let consensus_state = ConsensusState { + slot: 64.into(), + storage_root: CommitmentRoot::from_bytes(keccak256("storage").as_bytes()), + timestamp: Timestamp::from_nanoseconds( + datetime!(2023-08-20 0:00 UTC).unix_timestamp_nanos() as u64, + ) + .unwrap(), + current_sync_committee: current_sync_committee.to_committee().aggregate_pubkey, + next_sync_committee: next_sync_committee.to_committee().aggregate_pubkey, + }; + + let res = TrustedConsensusState::new( + consensus_state.clone(), + current_sync_committee.to_committee(), + false, + ); + assert!(res.is_ok(), "{:?}", res); + let state = res.unwrap(); + assert!(state.current_sync_committee.is_some()); + assert!(state.next_sync_committee.is_none()); + let res = TrustedConsensusState::new( + consensus_state.clone(), + current_sync_committee.to_committee(), + true, + ); + assert!(res.is_err(), "{:?}", res); + + let res = TrustedConsensusState::new( + consensus_state.clone(), + next_sync_committee.to_committee(), + true, + ); + assert!(res.is_ok(), "{:?}", res); + let state = res.unwrap(); + assert!(state.current_sync_committee.is_none()); + assert!(state.next_sync_committee.is_some()); + let res = TrustedConsensusState::new( + consensus_state.clone(), + next_sync_committee.to_committee(), + false, + ); + assert!(res.is_err(), "{:?}", res); + } + #[test] fn test_timestamp() { { From cd4fc7f80d216e503d26a0a6e8ceeeb91d61e1df Mon Sep 17 00:00:00 2001 From: Jun Kimura Date: Mon, 11 Nov 2024 11:53:09 +0900 Subject: [PATCH 07/11] move header's timestamp validation into `header::validate()` Signed-off-by: Jun Kimura --- crates/ibc/src/client_state.rs | 4 +++- crates/ibc/src/header.rs | 27 ++++++++++++++++++++++++++- crates/ibc/src/update.rs | 28 ++++------------------------ 3 files changed, 33 insertions(+), 26 deletions(-) diff --git a/crates/ibc/src/client_state.rs b/crates/ibc/src/client_state.rs index 77b7175..c564512 100644 --- a/crates/ibc/src/client_state.rs +++ b/crates/ibc/src/client_state.rs @@ -312,7 +312,10 @@ impl Ics2ClientState for ClientState Result { + let cc = self.build_context(ctx); let header = Header::::try_from(header)?; + header.validate(&cc)?; + let trusted_sync_committee = header.trusted_sync_committee; let consensus_state = match maybe_consensus_state( ctx, @@ -338,7 +341,6 @@ impl Ics2ClientState for ClientState( RawHeader::decode(buf).map_err(Error::Decode)?.try_into() } +impl Header { + pub fn validate(&self, ctx: &C) -> Result<(), Error> { + self.trusted_sync_committee.sync_committee.validate()?; + let header_timestamp = self.timestamp.into_tm_time().unwrap().unix_timestamp() as u64; + let timestamp = + compute_timestamp_at_slot(ctx, self.consensus_update.finalized_beacon_header().slot); + if header_timestamp != timestamp.0 { + return Err(Error::UnexpectedTimestamp( + timestamp.into(), + header_timestamp, + )); + } + Ok(()) + } +} + impl Ics02Header for Header { fn height(&self) -> ibc::Height { ibc::Height::new(0, self.execution_update.block_number.into()).unwrap() @@ -220,8 +239,14 @@ mod tests { consensus_update: update.clone(), execution_update: ExecutionUpdateInfo::default(), account_update: AccountUpdateInfo::default(), - timestamp: Timestamp::from_nanoseconds(1730729158 * 1_000_000_000).unwrap(), + timestamp: Timestamp::from_nanoseconds( + compute_timestamp_at_slot(&ctx, update.finalized_beacon_header().slot).0 + * 1_000_000_000, + ) + .unwrap(), }; + let res = header.validate(&ctx); + assert!(res.is_ok(), "header validation failed: {:?}", res); let any = IBCAny::from(header.clone()); let decoded = Header::<32>::try_from(any).unwrap(); assert_eq!(header, decoded); diff --git a/crates/ibc/src/update.rs b/crates/ibc/src/update.rs index 9c8a064..4608978 100644 --- a/crates/ibc/src/update.rs +++ b/crates/ibc/src/update.rs @@ -3,8 +3,7 @@ use crate::{ types::ConsensusUpdateInfo, }; use ethereum_consensus::{ - beacon::Slot, - compute::{compute_sync_committee_period_at_slot, compute_timestamp_at_slot}, + compute::compute_sync_committee_period_at_slot, context::ChainContext, types::{H256, U64}, }; @@ -23,20 +22,11 @@ pub fn apply_updates( consensus_update: ConsensusUpdateInfo, block_number: U64, account_storage_root: H256, - timestamp: Timestamp, + header_timestamp: Timestamp, ) -> Result<(ClientState, ConsensusState), Error> { let store_period = consensus_state.current_period(ctx); let update_finalized_slot = consensus_update.finalized_header.0.slot; let update_finalized_period = compute_sync_committee_period_at_slot(ctx, update_finalized_slot); - let timestamp = timestamp.into_tm_time().unwrap().unix_timestamp() as u64; - let finalized_header_timestamp: u64 = - compute_timestamp_at_slot(ctx, update_finalized_slot).into(); - if finalized_header_timestamp != timestamp { - return Err(Error::UnexpectedTimestamp( - finalized_header_timestamp, - timestamp, - )); - } // Let `store_period` be the period of the current sync committe of the consensus state, then the state transition is the following: // - If `store_period == update_finalized_period`, then the new consensus state will have the same sync committee info as the current consensus state. @@ -47,7 +37,7 @@ pub fn apply_updates( ConsensusState { slot: update_finalized_slot, storage_root: account_storage_root.0.to_vec().into(), - timestamp: wrap_compute_timestamp_at_slot(ctx, update_finalized_slot)?, + timestamp: header_timestamp, current_sync_committee: consensus_state.current_sync_committee.clone(), next_sync_committee: consensus_state.next_sync_committee.clone(), } @@ -59,7 +49,7 @@ pub fn apply_updates( ConsensusState { slot: update_finalized_slot, storage_root: account_storage_root.0.to_vec().into(), - timestamp: wrap_compute_timestamp_at_slot(ctx, update_finalized_slot)?, + timestamp: header_timestamp, current_sync_committee: consensus_state.next_sync_committee.clone(), next_sync_committee: update_next_sync_committee.aggregate_pubkey, } @@ -87,13 +77,3 @@ pub fn apply_updates( new_consensus_state.validate()?; Ok((new_client_state, new_consensus_state)) } - -fn wrap_compute_timestamp_at_slot( - ctx: &C, - slot: Slot, -) -> Result { - // NOTE: The return value of `compute_timestamp_at_slot`'s unit is seconds, - // so we need to convert it to nanoseconds. - let timestamp = compute_timestamp_at_slot(ctx, slot); - Ok(Timestamp::from_nanoseconds(timestamp.0 * 1_000_000_000)?) -} From 071109a167e5ecb9f653473335bbff5369904650 Mon Sep 17 00:00:00 2001 From: Jun Kimura Date: Mon, 11 Nov 2024 12:09:48 +0900 Subject: [PATCH 08/11] fix header timestamp validation Signed-off-by: Jun Kimura --- crates/ibc/src/errors.rs | 2 +- crates/ibc/src/header.rs | 59 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/crates/ibc/src/errors.rs b/crates/ibc/src/errors.rs index 95c8c61..49a9521 100644 --- a/crates/ibc/src/errors.rs +++ b/crates/ibc/src/errors.rs @@ -58,7 +58,7 @@ pub enum Error { target_height: Height, }, /// unexpected timestamp: expected={0} got={1} - UnexpectedTimestamp(u64, u64), + UnexpectedTimestamp(i128, i128), /// missing trusting period MissingTrustingPeriod, /// negative max clock drift diff --git a/crates/ibc/src/header.rs b/crates/ibc/src/header.rs index 90cc1b7..46e4eb0 100644 --- a/crates/ibc/src/header.rs +++ b/crates/ibc/src/header.rs @@ -78,13 +78,27 @@ pub fn decode_header( impl Header { pub fn validate(&self, ctx: &C) -> Result<(), Error> { self.trusted_sync_committee.sync_committee.validate()?; - let header_timestamp = self.timestamp.into_tm_time().unwrap().unix_timestamp() as u64; - let timestamp = + if self.timestamp.into_tm_time().is_none() { + return Err(Error::ZeroTimestampError); + } + let header_timestamp_nanos = self + .timestamp + .into_tm_time() + .unwrap() + .unix_timestamp_nanos(); + let timestamp_secs = compute_timestamp_at_slot(ctx, self.consensus_update.finalized_beacon_header().slot); - if header_timestamp != timestamp.0 { + let timestamp_nanos = i128::from(timestamp_secs.0) + .checked_mul(1_000_000_000) + .ok_or_else(|| { + Error::TimestampOverflowError( + ibc::timestamp::TimestampOverflowError::TimestampOverflow, + ) + })?; + if header_timestamp_nanos != timestamp_nanos { return Err(Error::UnexpectedTimestamp( - timestamp.into(), - header_timestamp, + timestamp_nanos, + header_timestamp_nanos, )); } Ok(()) @@ -266,6 +280,41 @@ mod tests { let res = Header::<32>::try_from(any); assert!(res.is_err(), "header with zero timestamp should fail"); } + + let update = gen_light_client_update_with_params::<32, _>( + &ctx, + base_signature_slot, + base_attested_slot, + base_finalized_epoch, + dummy_execution_state_root, + dummy_execution_block_number.into(), + current_sync_committee, + scm.get_committee(2), + true, + 32, + ); + let update = to_consensus_update_info(update); + let header = Header { + trusted_sync_committee: TrustedSyncCommittee { + height: ibc::Height::new(1, 1).unwrap(), + sync_committee: current_sync_committee.to_committee().clone(), + is_next: true, + }, + consensus_update: update.clone(), + execution_update: ExecutionUpdateInfo::default(), + account_update: AccountUpdateInfo::default(), + timestamp: Timestamp::from_nanoseconds( + compute_timestamp_at_slot(&ctx, update.finalized_beacon_header().slot).0 + * 1_000_000_000 + + 1, + ) + .unwrap(), + }; + let res = header.validate(&ctx); + assert!( + res.is_err(), + "header validation should fail for wrong timestamp" + ); } fn to_consensus_update_info( From 22051797833a86c9cd9f40b46cee0181382176c1 Mon Sep 17 00:00:00 2001 From: Jun Kimura Date: Mon, 11 Nov 2024 14:54:49 +0900 Subject: [PATCH 09/11] add tests for `apply_updates` Signed-off-by: Jun Kimura --- crates/ibc/src/errors.rs | 2 +- crates/ibc/src/update.rs | 305 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 304 insertions(+), 3 deletions(-) diff --git a/crates/ibc/src/errors.rs b/crates/ibc/src/errors.rs index 49a9521..d50f9a7 100644 --- a/crates/ibc/src/errors.rs +++ b/crates/ibc/src/errors.rs @@ -27,7 +27,7 @@ pub enum Error { Vec, ), /// consensus update doesn't have next sync committee: store_period={0} update_period={1} - NoNextSyncCommitteeInConsensusUpdate(u64, u64), + NoNextSyncCommitteeInConsensusUpdate(U64, U64), /// invalid current sync committee keys: expected={0:?} actual={1:?} InvalidCurrentSyncCommitteeKeys(PublicKey, PublicKey), /// invalid next sync committee keys: expected={0:?} actual={1:?} diff --git a/crates/ibc/src/update.rs b/crates/ibc/src/update.rs index 4608978..ee44875 100644 --- a/crates/ibc/src/update.rs +++ b/crates/ibc/src/update.rs @@ -56,8 +56,8 @@ pub fn apply_updates( } else { // Relayers must submit an update that contains the next sync committee if the update period is `store_period + 1`. return Err(Error::NoNextSyncCommitteeInConsensusUpdate( - store_period.into(), - update_finalized_period.into(), + store_period, + update_finalized_period, )); } } else { @@ -77,3 +77,304 @@ pub fn apply_updates( new_consensus_state.validate()?; Ok((new_client_state, new_consensus_state)) } + +#[cfg(test)] +mod tests { + use super::*; + use core::time::Duration; + use ethereum_consensus::beacon::Version; + use ethereum_consensus::compute::compute_timestamp_at_slot; + use ethereum_consensus::context::ChainContext; + use ethereum_consensus::fork::altair::ALTAIR_FORK_SPEC; + use ethereum_consensus::fork::bellatrix::BELLATRIX_FORK_SPEC; + use ethereum_consensus::fork::capella::CAPELLA_FORK_SPEC; + use ethereum_consensus::fork::deneb::DENEB_FORK_SPEC; + use ethereum_consensus::fork::{ForkParameter, ForkParameters}; + use ethereum_consensus::preset::minimal::PRESET; + use ethereum_consensus::types::Address; + use ethereum_consensus::{config, types::U64}; + use ethereum_light_client_verifier::updates::ConsensusUpdate; + use ethereum_light_client_verifier::{ + consensus::test_utils::{gen_light_client_update_with_params, MockSyncCommitteeManager}, + context::{Fraction, LightClientContext}, + updates::ConsensusUpdateInfo as EthConsensusUpdateInfo, + }; + use hex_literal::hex; + use ibc::core::ics23_commitment::commitment::CommitmentRoot; + use std::time::SystemTime; + + #[test] + pub fn test_apply_updates() { + let scm = MockSyncCommitteeManager::<32>::new(1, 6); + let ctx = LightClientContext::new_with_config( + config::minimal::get_config(), + Default::default(), + Default::default(), + Fraction::new(2, 3), + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs() + .into(), + ); + + let slots_per_period = ctx.slots_per_epoch() * ctx.epochs_per_sync_committee_period(); + let base_store_period = 3u64; + let base_store_slot = U64(base_store_period) * slots_per_period; + let base_finalized_epoch = base_store_slot / ctx.slots_per_epoch() + 1; + let base_attested_slot = (base_finalized_epoch + 2) * ctx.slots_per_epoch(); + let base_signature_slot = base_attested_slot + 1; + + let current_sync_committee = scm.get_committee(base_store_period); + let dummy_execution_state_root = [1u8; 32].into(); + let dummy_execution_block_number = 1; + + let client_state = + ClientState::<{ ethereum_consensus::preset::minimal::PRESET.SYNC_COMMITTEE_SIZE }> { + genesis_validators_root: keccak256("genesis_validators_root"), + min_sync_committee_participants: 1.into(), + genesis_time: 1.into(), + fork_parameters: ForkParameters::new( + Version([0, 0, 0, 1]), + vec![ + ForkParameter::new(Version([1, 0, 0, 1]), U64(0), ALTAIR_FORK_SPEC), + ForkParameter::new(Version([2, 0, 0, 1]), U64(0), BELLATRIX_FORK_SPEC), + ForkParameter::new(Version([3, 0, 0, 1]), U64(0), CAPELLA_FORK_SPEC), + ForkParameter::new(Version([4, 0, 0, 1]), U64(0), DENEB_FORK_SPEC), + ], + ) + .unwrap(), + seconds_per_slot: PRESET.SECONDS_PER_SLOT, + slots_per_epoch: PRESET.SLOTS_PER_EPOCH, + epochs_per_sync_committee_period: PRESET.EPOCHS_PER_SYNC_COMMITTEE_PERIOD, + ibc_address: Address(hex!("ff77D90D6aA12db33d3Ba50A34fB25401f6e4c4F")), + ibc_commitments_slot: keccak256("ibc_commitments_slot"), + trust_level: Fraction::new(2, 3), + trusting_period: Duration::from_secs(60 * 60 * 27), + max_clock_drift: Duration::from_secs(60), + latest_execution_block_number: 1.into(), + frozen_height: None, + consensus_verifier: Default::default(), + execution_verifier: Default::default(), + }; + + let consensus_state = ConsensusState { + slot: base_store_slot, + storage_root: CommitmentRoot::from_bytes(keccak256("storage_root").as_bytes()), + timestamp: Timestamp::from_nanoseconds( + compute_timestamp_at_slot(&ctx, base_store_slot).0 * 1_000_000_000, + ) + .unwrap(), + current_sync_committee: scm + .get_committee(base_store_period) + .to_committee() + .aggregate_pubkey, + next_sync_committee: scm + .get_committee(base_store_period + 1) + .to_committee() + .aggregate_pubkey, + }; + + { + // store_period == finalized_period == attested_period == signature_period + let update = to_consensus_update_info(gen_light_client_update_with_params::<32, _>( + &ctx, + base_signature_slot, + base_attested_slot, + base_finalized_epoch, + dummy_execution_state_root, + dummy_execution_block_number.into(), + current_sync_committee, + scm.get_committee(base_store_period + 1), + true, + 32, + )); + let new_block_number = 2.into(); + let res = apply_updates( + &ctx, + &client_state, + &consensus_state, + update.clone(), + new_block_number, + H256::from_slice(&[1u8; 32]), + Timestamp::from_nanoseconds( + compute_timestamp_at_slot(&ctx, update.finalized_header.0.slot).0 + * 1_000_000_000, + ) + .unwrap(), + ); + assert!(res.is_ok(), "{:?}", res); + let (new_client_state, new_consensus_state) = res.unwrap(); + assert_eq!( + new_client_state.latest_execution_block_number, + new_block_number + ); + assert_eq!(new_consensus_state.slot, update.finalized_header.0.slot); + // sync committee info should be the same as the current consensus state + assert_eq!( + new_consensus_state.current_sync_committee, + scm.get_committee(base_store_period) + .to_committee() + .aggregate_pubkey + ); + assert_eq!( + new_consensus_state.next_sync_committee, + scm.get_committee(base_store_period + 1) + .to_committee() + .aggregate_pubkey + ); + } + { + // store_period + 1 == finalized_period == attested_period == signature_period + let update = to_consensus_update_info(gen_light_client_update_with_params::<32, _>( + &ctx, + base_signature_slot + slots_per_period, + base_attested_slot + slots_per_period, + base_finalized_epoch + ctx.epochs_per_sync_committee_period(), + dummy_execution_state_root, + dummy_execution_block_number.into(), + current_sync_committee, + scm.get_committee(base_store_period + 2), + true, + 32, + )); + let new_block_number = 2.into(); + let res = apply_updates( + &ctx, + &client_state, + &consensus_state, + update.clone(), + new_block_number, + H256::from_slice(&[1u8; 32]), + Timestamp::from_nanoseconds( + compute_timestamp_at_slot(&ctx, update.finalized_header.0.slot).0 + * 1_000_000_000, + ) + .unwrap(), + ); + assert!(res.is_ok(), "{:?}", res); + let (new_client_state, new_consensus_state) = res.unwrap(); + assert_eq!( + new_client_state.latest_execution_block_number, + new_block_number + ); + assert_eq!(new_consensus_state.slot, update.finalized_header.0.slot); + // sync committee info should be the same as the current consensus state + assert_eq!( + new_consensus_state.current_sync_committee, + scm.get_committee(base_store_period + 1) + .to_committee() + .aggregate_pubkey + ); + assert_eq!( + new_consensus_state.next_sync_committee, + scm.get_committee(base_store_period + 2) + .to_committee() + .aggregate_pubkey + ); + } + { + // store_period + 1 == finalized_period == attested_period == signature_period + // but the update has no next sync committee + let update = to_consensus_update_info(gen_light_client_update_with_params::<32, _>( + &ctx, + base_signature_slot + slots_per_period, + base_attested_slot + slots_per_period, + base_finalized_epoch + ctx.epochs_per_sync_committee_period(), + dummy_execution_state_root, + dummy_execution_block_number.into(), + current_sync_committee, + scm.get_committee(base_store_period + 2), + false, + 32, + )); + let new_block_number = 2.into(); + let res = apply_updates( + &ctx, + &client_state, + &consensus_state, + update.clone(), + new_block_number, + H256::from_slice(&[1u8; 32]), + Timestamp::from_nanoseconds( + compute_timestamp_at_slot(&ctx, update.finalized_header.0.slot).0 + * 1_000_000_000, + ) + .unwrap(), + ); + assert!(res.is_err(), "{:?}", res); + if let Err(Error::NoNextSyncCommitteeInConsensusUpdate(store_period, update_period)) = + res + { + assert_eq!(store_period.0, base_store_period); + assert_eq!(update_period.0, base_store_period + 1); + } else { + panic!("unexpected error: {:?}", res); + } + } + { + // finalized_period - 1 == store_period == attested_period == signature_period + let update = to_consensus_update_info(gen_light_client_update_with_params::<32, _>( + &ctx, + base_signature_slot, + base_attested_slot, + base_finalized_epoch - ctx.epochs_per_sync_committee_period(), + dummy_execution_state_root, + dummy_execution_block_number.into(), + current_sync_committee, + scm.get_committee(base_store_period), + true, + 32, + )); + let new_block_number = 2.into(); + let res = apply_updates( + &ctx, + &client_state, + &consensus_state, + update.clone(), + new_block_number, + H256::from_slice(&[1u8; 32]), + Timestamp::from_nanoseconds( + compute_timestamp_at_slot(&ctx, update.finalized_header.0.slot).0 + * 1_000_000_000, + ) + .unwrap(), + ); + if let Err(Error::StoreNotSupportedFinalizedPeriod(store_period, update_period)) = res { + assert_eq!(store_period.0, base_store_period); + assert_eq!( + update_period, + compute_sync_committee_period_at_slot( + &ctx, + update.finalized_beacon_header().slot + ) + ); + } else { + panic!("unexpected error: {:?}", res); + } + } + } + + fn keccak256(s: &str) -> H256 { + use tiny_keccak::{Hasher, Keccak}; + let mut hasher = Keccak::v256(); + let mut output = [0u8; 32]; + hasher.update(s.as_bytes()); + hasher.finalize(&mut output); + H256::from_slice(&output) + } + + fn to_consensus_update_info( + consensus_update: EthConsensusUpdateInfo, + ) -> ConsensusUpdateInfo { + ConsensusUpdateInfo { + attested_header: consensus_update.light_client_update.attested_header, + next_sync_committee: consensus_update.light_client_update.next_sync_committee, + finalized_header: consensus_update.light_client_update.finalized_header, + sync_aggregate: consensus_update.light_client_update.sync_aggregate, + signature_slot: consensus_update.light_client_update.signature_slot, + finalized_execution_root: consensus_update.finalized_execution_root, + finalized_execution_branch: consensus_update.finalized_execution_branch, + } + } +} From 41be5a9848d5d7c8877cffd9b3515c624d8ccb1c Mon Sep 17 00:00:00 2001 From: Jun Kimura Date: Mon, 11 Nov 2024 21:50:47 +0900 Subject: [PATCH 10/11] update verifier crate to v0.1.5 Signed-off-by: Jun Kimura --- crates/ibc/Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/ibc/Cargo.toml b/crates/ibc/Cargo.toml index cdff4e6..df45be0 100644 --- a/crates/ibc/Cargo.toml +++ b/crates/ibc/Cargo.toml @@ -16,10 +16,10 @@ ssz-rs = { git = "https://github.com/bluele/ssz_rs", branch = "serde-no-std", de hex = { version = "0.4.3", default-features = false } ethereum-ibc-proto = { path = "../../proto", default-features = false } -ethereum-consensus = { git = "https://github.com/datachainlab/ethereum-light-client-rs", rev = "6c2b3b0f1a58c4df3eedeb9a36de0e7fcab25007", default-features = false } -ethereum-light-client-verifier = { git = "https://github.com/datachainlab/ethereum-light-client-rs", rev = "6c2b3b0f1a58c4df3eedeb9a36de0e7fcab25007", default-features = false } +ethereum-consensus = { git = "https://github.com/datachainlab/ethereum-light-client-rs", rev = "v0.1.5", default-features = false } +ethereum-light-client-verifier = { git = "https://github.com/datachainlab/ethereum-light-client-rs", rev = "v0.1.5", default-features = false } [dev-dependencies] time = { version = "0.3", default-features = false, features = ["macros", "parsing"] } hex-literal = "0.4.1" -ethereum-light-client-verifier = { git = "https://github.com/datachainlab/ethereum-light-client-rs", rev = "6c2b3b0f1a58c4df3eedeb9a36de0e7fcab25007", default-features = false, features = ["test-utils"] } +ethereum-light-client-verifier = { git = "https://github.com/datachainlab/ethereum-light-client-rs", rev = "v0.1.5", default-features = false, features = ["test-utils"] } From c7f469b6da1bf2e09f7f29667e4c4779fb4f45f7 Mon Sep 17 00:00:00 2001 From: Jun Kimura Date: Tue, 12 Nov 2024 09:31:25 +0900 Subject: [PATCH 11/11] fix `initialise()` to not be able to initialise with the frozen client Signed-off-by: Jun Kimura --- crates/ibc/src/client_state.rs | 3 +++ crates/ibc/src/errors.rs | 2 ++ 2 files changed, 5 insertions(+) diff --git a/crates/ibc/src/client_state.rs b/crates/ibc/src/client_state.rs index c564512..deb4c66 100644 --- a/crates/ibc/src/client_state.rs +++ b/crates/ibc/src/client_state.rs @@ -301,6 +301,9 @@ impl Ics2ClientState for ClientState Result, ClientError> { self.validate()?; + if self.is_frozen() { + return Err(Error::CannotInitializeFrozenClient.into()); + } let consensus_state = ConsensusState::try_from(consensus_state)?; consensus_state.validate()?; Ok(ConsensusState::into_box(consensus_state)) diff --git a/crates/ibc/src/errors.rs b/crates/ibc/src/errors.rs index d50f9a7..1272333 100644 --- a/crates/ibc/src/errors.rs +++ b/crates/ibc/src/errors.rs @@ -111,6 +111,8 @@ pub enum Error { ProtoMissingFieldError(String), /// unknown message type: `{0}` UnknownMessageType(String), + /// cannot initialize frozen client + CannotInitializeFrozenClient, } impl Error {