diff --git a/Cargo.lock b/Cargo.lock index 924c1b4..dd0e3e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -452,12 +452,13 @@ dependencies = [ [[package]] name = "ethereum-consensus" version = "0.1.0" -source = "git+https://github.com/datachainlab/ethereum-light-client-rs?rev=v0.1.6#e2026b51afde85d4bcf48633e22c7d7510d4ae69" +source = "git+https://github.com/datachainlab/ethereum-light-client-rs?rev=v0.1.7#4645ecd3cd2d5e1515826ecb6f81f7801888e51a" dependencies = [ "displaydoc", "hex", "milagro_bls", "primitive-types", + "rs_merkle", "serde", "sha2 0.10.7", "ssz-rs", @@ -469,16 +470,21 @@ name = "ethereum-elc" version = "0.1.0" dependencies = [ "displaydoc", + "ethereum-consensus", "ethereum-ibc", + "ethereum-light-client-verifier", + "hex-literal", "ibc", + "lcp-types", "light-client", - "tiny-keccak 1.5.0", + "store", + "tiny-keccak 2.0.2", ] [[package]] name = "ethereum-ibc" version = "0.1.0" -source = "git+https://github.com/datachainlab/ethereum-ibc-rs?rev=v0.0.18#80ea9cc3fb55a8231d0de952df42bffd35d30c90" +source = "git+https://github.com/datachainlab/ethereum-ibc-rs?rev=v0.0.19#06864ca06431906fdad149839cda52ba43da48d2" dependencies = [ "bytes", "displaydoc", @@ -498,7 +504,7 @@ dependencies = [ [[package]] name = "ethereum-ibc-proto" version = "0.1.0" -source = "git+https://github.com/datachainlab/ethereum-ibc-rs?rev=v0.0.18#80ea9cc3fb55a8231d0de952df42bffd35d30c90" +source = "git+https://github.com/datachainlab/ethereum-ibc-rs?rev=v0.0.19#06864ca06431906fdad149839cda52ba43da48d2" dependencies = [ "prost", "serde", @@ -507,12 +513,13 @@ dependencies = [ [[package]] name = "ethereum-light-client-verifier" version = "0.1.0" -source = "git+https://github.com/datachainlab/ethereum-light-client-rs?rev=v0.1.6#e2026b51afde85d4bcf48633e22c7d7510d4ae69" +source = "git+https://github.com/datachainlab/ethereum-light-client-rs?rev=v0.1.7#4645ecd3cd2d5e1515826ecb6f81f7801888e51a" dependencies = [ "displaydoc", "ethereum-consensus", "patricia-merkle-trie", "primitive-types", + "rand", "rlp", "serde", "trie-db", @@ -524,6 +531,9 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" dependencies = [ + "byteorder", + "rand", + "rustc-hex", "static_assertions", ] @@ -771,6 +781,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + [[package]] name = "impl-serde" version = "0.4.0" @@ -1030,9 +1049,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8e946cc0cc711189c0b0249fb8b599cbeeab9784d83c415719368bb8d4ac64" dependencies = [ "arrayvec", + "bitvec", "byte-slice-cast", "impl-trait-for-tuples", "parity-scale-codec-derive", + "serde", ] [[package]] @@ -1117,6 +1138,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f3486ccba82358b11a77516035647c34ba167dfa53312630de83b12bd4f3d66" dependencies = [ "fixed-hash", + "impl-codec", "impl-serde", "uint", ] @@ -1240,6 +1262,8 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ + "libc", + "rand_chacha", "rand_core", ] @@ -1258,6 +1282,9 @@ name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] [[package]] name = "rand_xorshift" @@ -1287,6 +1314,15 @@ dependencies = [ "rustc-hex", ] +[[package]] +name = "rs_merkle" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b241d2e59b74ef9e98d94c78c47623d04c8392abaf82014dfd372a16041128f" +dependencies = [ + "sha2 0.10.7", +] + [[package]] name = "ruint" version = "1.11.1" @@ -1845,6 +1881,7 @@ dependencies = [ "hash-db", "hashbrown 0.12.3", "log", + "rustc-hex", "smallvec", ] diff --git a/Cargo.toml b/Cargo.toml index d87b300..2e8b18f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,14 @@ edition = "2021" [dependencies] ibc = { version = "0.29.0", default-features = false, features = ["serde"] } displaydoc = { version = "0.2", default-features = false } -tiny-keccak = { version = "1.4" } +tiny-keccak = { version = "2.0.2", default-features = false } light-client = { git = "https://github.com/datachainlab/lcp", rev = "v0.2.11", default-features = false, features = ["ibc"] } -ethereum-ibc = { git = "https://github.com/datachainlab/ethereum-ibc-rs", rev = "v0.0.18", default-features = false } +ethereum-ibc = { git = "https://github.com/datachainlab/ethereum-ibc-rs", rev = "v0.0.19", default-features = false } + +[dev-dependencies] +hex-literal = "0.4.1" +lcp-types = { git = "https://github.com/datachainlab/lcp", rev = "v0.2.11", default-features = false, features = ["std"] } +store = { git = "https://github.com/datachainlab/lcp.git", rev = "v0.2.11", default-features = false } +ethereum-consensus = { git = "https://github.com/datachainlab/ethereum-light-client-rs", rev = "v0.1.7", default-features = false, features = ["prover"] } +ethereum-light-client-verifier = { git = "https://github.com/datachainlab/ethereum-light-client-rs", rev = "v0.1.7", default-features = false, features = ["test-utils"] } diff --git a/src/client.rs b/src/client.rs index 11dd667..30ff90d 100644 --- a/src/client.rs +++ b/src/client.rs @@ -29,7 +29,7 @@ use light_client::{ CreateClientResult, HostClientReader, LightClient, MisbehaviourData, UpdateStateData, VerifyMembershipResult, VerifyNonMembershipResult, }; -use tiny_keccak::Keccak; +use tiny_keccak::{Hasher, Keccak}; pub struct EthereumLightClient; @@ -117,7 +117,6 @@ impl LightClient for EthereumLightClient LightClient for EthereumLightClient LightClient for EthereumLightClient LightClient for EthereumLightClient EthereumLightClient ) .map_err(|e| { Error::ICS02(ClientError::HeaderVerificationFailure { - reason: e.to_string(), + reason: format!("{:?}", e), }) })?; @@ -330,7 +329,7 @@ impl EthereumLightClient ) .map_err(|e| { Error::ICS02(ClientError::MisbehaviourHandlingFailure { - reason: e.to_string(), + reason: format!("{:?}", e), }) })?; let new_client_state = @@ -388,9 +387,677 @@ impl EthereumLightClient } fn keccak256(bz: &[u8]) -> [u8; 32] { - let mut keccak = Keccak::new_keccak256(); - let mut result = [0u8; 32]; - keccak.update(bz); - keccak.finalize(result.as_mut()); - result + let mut hasher = Keccak::v256(); + let mut output = [0u8; 32]; + hasher.update(bz); + hasher.finalize(&mut output); + output +} + +#[cfg(test)] +mod tests { + use super::*; + use core::time::Duration; + use ethereum_consensus::beacon::Version; + use ethereum_consensus::compute::{ + compute_sync_committee_period_at_slot, 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, H256}; + use ethereum_consensus::{config, types::U64}; + use ethereum_ibc::client_state::ETHEREUM_CLIENT_REVISION_NUMBER; + use ethereum_ibc::commitment::decode_eip1184_rlp_proof; + use ethereum_ibc::types::{ + AccountUpdateInfo, ConsensusUpdateInfo, ExecutionUpdateInfo, TrustedSyncCommittee, + }; + use ethereum_light_client_verifier::consensus::test_utils::{ + gen_light_client_update, gen_light_client_update_with_params, + }; + use ethereum_light_client_verifier::context::ConsensusVerificationContext; + use ethereum_light_client_verifier::execution::ExecutionVerifier; + use ethereum_light_client_verifier::misbehaviour::{ + FinalizedHeaderMisbehaviour, Misbehaviour, NextSyncCommitteeMisbehaviour, + }; + use ethereum_light_client_verifier::updates::{self, ConsensusUpdate}; + use ethereum_light_client_verifier::{ + consensus::test_utils::MockSyncCommitteeManager, + context::{Fraction, LightClientContext}, + }; + use hex_literal::hex; + use ibc::timestamp::Timestamp; + use light_client::{ClientReader, HostContext, UpdateClientResult}; + use std::collections::BTreeMap; + use std::time::SystemTime; + use store::KVStore; + + #[derive(Debug, Clone)] + pub struct MockContext { + pub client_state: Option>, + pub consensus_states: BTreeMap, + pub timestamp: Time, + } + + impl MockContext { + pub fn new(timestamp: Time) -> Self { + Self { + client_state: None, + consensus_states: BTreeMap::new(), + timestamp, + } + } + + pub fn set_timestamp(&mut self, timestamp_secs: U64) { + self.timestamp = Time::from_unix_timestamp(timestamp_secs.0 as i64, 0).unwrap(); + } + } + + impl HostContext for MockContext { + fn host_timestamp(&self) -> Time { + self.timestamp + } + } + + impl KVStore for MockContext { + fn set(&mut self, _key: Vec, _value: Vec) { + unimplemented!() + } + + fn get(&self, _key: &[u8]) -> Option> { + unimplemented!() + } + + fn remove(&mut self, _key: &[u8]) { + unimplemented!() + } + } + + impl ClientReader for MockContext { + fn client_state(&self, client_id: &ClientId) -> Result { + let cs = self + .client_state + .clone() + .ok_or_else(|| light_client::Error::client_state_not_found(client_id.clone()))?; + Ok(IBCAny::from(cs).into()) + } + + fn consensus_state( + &self, + client_id: &ClientId, + height: &Height, + ) -> Result { + let state = self + .consensus_states + .get(height) + .ok_or_else(|| { + light_client::Error::consensus_state_not_found(client_id.clone(), *height) + })? + .clone(); + Ok(IBCAny::from(state).into()) + } + } + + impl HostClientReader for MockContext {} + + #[test] + pub fn test_client() { + let scm = MockSyncCommitteeManager::<32>::new(1, 6); + let lctx = LightClientContext::new_with_config( + config::minimal::get_config(), + keccak256("genesis_validators_root"), + U64(1578009600), + Fraction::new(2, 3), + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs() + .into(), + ); + + let slots_per_period = lctx.slots_per_epoch() * lctx.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 / lctx.slots_per_epoch() + 1; + let base_attested_slot = (base_finalized_epoch + 2) * lctx.slots_per_epoch(); + let base_signature_slot = base_attested_slot + 1; + + let client_state = + ClientState::<{ ethereum_consensus::preset::minimal::PRESET.SYNC_COMMITTEE_SIZE }> { + genesis_validators_root: lctx.genesis_validators_root(), + min_sync_committee_participants: 1.into(), + genesis_time: lctx.genesis_time(), + 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!("a7f733a4fEA1071f58114b203F57444969b86524")), + ibc_commitments_slot: H256(hex!( + "1ee222554989dda120e26ecacf756fe1235cd8d726706b57517715dde4f0c900" + )), + 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: hex!("27cd08827e6bf1e435832f4b2660107beb562314287b3fa534f3b189574c0cca") + .to_vec() + .into(), + timestamp: Timestamp::from_nanoseconds( + compute_timestamp_at_slot(&lctx, 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, + }; + + let lc = EthereumLightClient::< + { ethereum_consensus::preset::minimal::PRESET.SYNC_COMMITTEE_SIZE }, + >; + + let base_ctx = { + let mut ctx = MockContext::< + { ethereum_consensus::preset::minimal::PRESET.SYNC_COMMITTEE_SIZE }, + >::new( + Time::from_unix_timestamp(lctx.genesis_time().0 as i64, 0).unwrap() + ); + let res = lc.create_client( + &ctx, + IBCAny::from(client_state.clone()).into(), + IBCAny::from(consensus_state.clone()).into(), + ); + assert!(res.is_ok(), "{:?}", res); + ctx.client_state = Some(client_state.clone()); + ctx.consensus_states + .insert(res.unwrap().height, consensus_state); + ctx + }; + let client_id = ClientId::new(eth_client_type().as_str(), 0).unwrap(); + { + // Update client with a header that has the same period + + let mut ctx = base_ctx.clone(); + let execution_account = get_execution_account_info(); + execution_account.validate(&ctx.client_state.as_ref().unwrap().ibc_address); + let (consensus_update, execution_update) = gen_light_client_update::<32, _>( + &lctx, + base_signature_slot, + base_attested_slot, + base_finalized_epoch, + execution_account.state_root, + 2.into(), + false, + &scm, + ); + let header = + Header::<{ ethereum_consensus::preset::minimal::PRESET.SYNC_COMMITTEE_SIZE }> { + trusted_sync_committee: get_trusted_sync_committee( + &lctx, + &ctx, + &scm, + consensus_update.signature_slot(), + ), + consensus_update: convert_consensus_update(&consensus_update), + execution_update: convert_execution_update(&execution_update), + account_update: execution_account.account_update, + timestamp: Timestamp::from_nanoseconds( + compute_timestamp_at_slot( + &lctx, + consensus_update.finalized_beacon_header().slot, + ) + .0 * 1_000_000_000, + ) + .unwrap(), + }; + + ctx.set_timestamp( + compute_timestamp_at_slot(&lctx, header.consensus_update.signature_slot) + 12, + ); + let res = lc.update_client( + &ctx, + client_id.clone(), + IBCAny::from(ClientMessage::Header(header.clone())).into(), + ); + assert!(res.is_ok(), "{:?}", res); + let (new_client_state, new_consensus_state, height) = match res.unwrap() { + UpdateClientResult::UpdateState(data) => ( + data.new_any_client_state, + data.new_any_consensus_state, + data.height, + ), + _ => unreachable!(), + }; + let client_state = ClientState::<32>::try_from(IBCAny::from(new_client_state)).unwrap(); + assert_eq!(client_state.latest_execution_block_number, 2.into()); + let consensus_state = + ConsensusState::try_from(IBCAny::from(new_consensus_state)).unwrap(); + assert_eq!( + consensus_state.slot, + header.consensus_update.finalized_beacon_header().slot + ); + assert_eq!( + consensus_state.current_sync_committee, + scm.get_committee(consensus_state.current_period(&lctx).into()) + .to_committee() + .aggregate_pubkey + ); + assert_eq!( + consensus_state.next_sync_committee, + scm.get_committee((consensus_state.current_period(&lctx) + 1).into()) + .to_committee() + .aggregate_pubkey + ); + ctx.client_state = Some(client_state); + ctx.consensus_states.insert(height, consensus_state); + + // Update client with a header that has a new period + + let base_finalized_epoch = + U64(base_store_period + 1) * slots_per_period / lctx.slots_per_epoch() + 1; + let base_attested_slot = (base_finalized_epoch + 2) * lctx.slots_per_epoch(); + let base_signature_slot = base_attested_slot + 1; + + let execution_account = get_execution_account_info(); + execution_account.validate(&ctx.client_state.as_ref().unwrap().ibc_address); + let (consensus_update, execution_update) = gen_light_client_update::<32, _>( + &lctx, + base_signature_slot, + base_attested_slot, + base_finalized_epoch, + execution_account.state_root, + 3.into(), + true, + &scm, + ); + let header = + Header::<{ ethereum_consensus::preset::minimal::PRESET.SYNC_COMMITTEE_SIZE }> { + trusted_sync_committee: get_trusted_sync_committee( + &lctx, + &ctx, + &scm, + consensus_update.signature_slot(), + ), + consensus_update: convert_consensus_update(&consensus_update), + execution_update: convert_execution_update(&execution_update), + account_update: execution_account.account_update, + timestamp: Timestamp::from_nanoseconds( + compute_timestamp_at_slot( + &lctx, + consensus_update.finalized_beacon_header().slot, + ) + .0 * 1_000_000_000, + ) + .unwrap(), + }; + ctx.set_timestamp( + compute_timestamp_at_slot(&lctx, header.consensus_update.signature_slot) + 12, + ); + let res = lc.update_client( + &ctx, + client_id.clone(), + IBCAny::from(ClientMessage::Header(header.clone())).into(), + ); + assert!(res.is_ok(), "{:?}", res); + let (new_client_state, new_consensus_state, height) = match res.unwrap() { + UpdateClientResult::UpdateState(data) => ( + data.new_any_client_state, + data.new_any_consensus_state, + data.height, + ), + _ => unreachable!(), + }; + let client_state = ClientState::<32>::try_from(IBCAny::from(new_client_state)).unwrap(); + assert_eq!(client_state.latest_execution_block_number, 3.into()); + let consensus_state = + ConsensusState::try_from(IBCAny::from(new_consensus_state)).unwrap(); + assert_eq!( + consensus_state.slot, + header.consensus_update.finalized_beacon_header().slot + ); + assert_eq!( + consensus_state.current_sync_committee, + scm.get_committee(consensus_state.current_period(&lctx).into()) + .to_committee() + .aggregate_pubkey + ); + assert_eq!( + consensus_state.next_sync_committee, + scm.get_committee((consensus_state.current_period(&lctx) + 1).into()) + .to_committee() + .aggregate_pubkey + ); + ctx.client_state = Some(client_state); + ctx.consensus_states.insert(height, consensus_state); + } + // Detect FinalizedHeaderMisbehaviour + { + let mut ctx = base_ctx.clone(); + let (update_1, _) = gen_light_client_update_with_params::<32, _>( + &lctx, + base_signature_slot, + base_attested_slot, + base_finalized_epoch, + [1; 32].into(), + 4.into(), + scm.get_committee(base_store_period), + scm.get_committee(base_store_period + 1), + true, + 32, + ); + let (update_2, _) = gen_light_client_update_with_params::<32, _>( + &lctx, + base_signature_slot, + base_attested_slot, + base_finalized_epoch, + [2; 32].into(), + 4.into(), + scm.get_committee(base_store_period), + scm.get_committee(base_store_period + 1), + true, + 32, + ); + let misbehaviour = Misbehaviour::FinalizedHeader(FinalizedHeaderMisbehaviour { + consensus_update_1: convert_consensus_update(&update_1), + consensus_update_2: convert_consensus_update(&update_2), + }); + ctx.set_timestamp(compute_timestamp_at_slot(&lctx, base_signature_slot) + 12); + let res = lc.update_client( + &ctx, + client_id.clone(), + IBCAny::from(ClientMessage::Misbehaviour( + ethereum_ibc::misbehaviour::Misbehaviour { + client_id: client_id.clone().into(), + trusted_sync_committee: get_trusted_sync_committee( + &lctx, + &ctx, + &scm, + base_signature_slot, + ), + data: misbehaviour, + }, + )) + .into(), + ); + assert!(res.is_ok(), "{:?}", res); + let data = match res.unwrap() { + UpdateClientResult::Misbehaviour(data) => data, + _ => unreachable!(), + }; + let new_client_state = + ClientState::<32>::try_from(IBCAny::from(data.new_any_client_state.clone())) + .unwrap(); + assert!(new_client_state.frozen_height.is_some()); + } + // Detect NextSyncCommitteeMisbehaviour + { + let mut ctx = base_ctx.clone(); + let (update_1, _) = gen_light_client_update_with_params::<32, _>( + &lctx, + base_signature_slot, + base_attested_slot, + base_finalized_epoch, + [1; 32].into(), + 4.into(), + scm.get_committee(base_store_period), + scm.get_committee(base_store_period + 1), + true, + 32, + ); + let (update_2, _) = gen_light_client_update_with_params::<32, _>( + &lctx, + base_signature_slot, + base_attested_slot, + base_finalized_epoch, + [1; 32].into(), + 4.into(), + scm.get_committee(base_store_period), + scm.get_committee(base_store_period + 2), // invalid next sync committee + true, + 32, + ); + let misbehaviour = Misbehaviour::NextSyncCommittee(NextSyncCommitteeMisbehaviour { + consensus_update_1: convert_consensus_update(&update_1), + consensus_update_2: convert_consensus_update(&update_2), + }); + ctx.set_timestamp(compute_timestamp_at_slot(&lctx, base_signature_slot) + 12); + let res = lc.update_client( + &ctx, + client_id.clone(), + IBCAny::from(ClientMessage::Misbehaviour( + ethereum_ibc::misbehaviour::Misbehaviour { + client_id: client_id.clone().into(), + trusted_sync_committee: get_trusted_sync_committee( + &lctx, + &ctx, + &scm, + base_signature_slot, + ), + data: misbehaviour, + }, + )) + .into(), + ); + assert!(res.is_ok(), "{:?}", res); + let data = match res.unwrap() { + UpdateClientResult::Misbehaviour(data) => data, + _ => unreachable!(), + }; + let new_client_state = + ClientState::<32>::try_from(IBCAny::from(data.new_any_client_state.clone())) + .unwrap(); + assert!(new_client_state.frozen_height.is_some()); + } + // Verify membership of state + { + let mut ctx = base_ctx.clone(); + ctx.set_timestamp(compute_timestamp_at_slot(&lctx, base_signature_slot) + 12); + let (path, proof, value) = get_membership_proof(); + let res = lc.verify_membership( + &ctx, + client_id.clone(), + "ibc".as_bytes().to_vec(), + path, + value, + Height::new(ETHEREUM_CLIENT_REVISION_NUMBER, 1), + proof, + ); + assert!(res.is_ok(), "{:?}", res); + } + // Verify non-membership of state + { + let mut ctx = base_ctx.clone(); + ctx.set_timestamp(compute_timestamp_at_slot(&lctx, base_signature_slot) + 12); + let (path, proof) = get_non_membership_proof(); + let res = lc.verify_non_membership( + &ctx, + client_id.clone(), + "ibc".as_bytes().to_vec(), + path, + Height::new(ETHEREUM_CLIENT_REVISION_NUMBER, 1), + proof, + ); + assert!(res.is_ok(), "{:?}", res); + } + // Verify membership of non-existing state (should fail) + { + let mut ctx = base_ctx.clone(); + ctx.set_timestamp(compute_timestamp_at_slot(&lctx, base_signature_slot) + 12); + let (path, proof) = get_non_membership_proof(); + let res = lc.verify_membership( + &ctx, + client_id.clone(), + "ibc".as_bytes().to_vec(), + path, + Default::default(), + Height::new(ETHEREUM_CLIENT_REVISION_NUMBER, 1), + proof, + ); + assert!(res.is_err(), "{:?}", res); + } + // Verify non-membership of existing state (should fail) + { + let mut ctx = base_ctx.clone(); + ctx.set_timestamp(compute_timestamp_at_slot(&lctx, base_signature_slot) + 12); + let (path, proof, _) = get_membership_proof(); + let res = lc.verify_non_membership( + &ctx, + client_id.clone(), + "ibc".as_bytes().to_vec(), + path, + Height::new(ETHEREUM_CLIENT_REVISION_NUMBER, 1), + proof, + ); + assert!(res.is_err(), "{:?}", res); + } + } + + struct ExecutionAccountInfo { + state_root: H256, + account_update: AccountUpdateInfo, + } + + impl ExecutionAccountInfo { + pub fn validate(&self, ibc_address: &Address) { + let valid = ExecutionVerifier + .verify_account_storage_root( + self.state_root, + ibc_address, + self.account_update.account_proof.clone(), + self.account_update.account_storage_root, + ) + .unwrap(); + assert!(valid, "Execution account verification failed"); + } + } + + fn get_execution_account_info() -> ExecutionAccountInfo { + ExecutionAccountInfo { + state_root: H256(hex!( + "464981a459f94d85c83a005a8ef63631bb7879a135f8b0a62cbc64582a38e65c" + )), + account_update: AccountUpdateInfo { + account_proof: decode_eip1184_rlp_proof( + hex!("f9023ff901d1a04398e078259fe71d54d3e118b8b70126176db7a6a666049f9b0ed6bad81db6daa068be278e8fb14b2e6708c2f0484d866462468c182c1b09d9fb1259844b63cb1ca03dccdb9c963884c8c0b36ff9f770c0b4ba702df8647c7555d64afc3657808953a0d14aef547f57a65e31e6756b1012fd2df269dceec0a45be53c02273b208929efa09c8d1c426dc97706b5c561f610b6d0c9289d7e77ea1a3324955684b919d742d480a0bfe090c221fa27abcc7f0b02bd0aecf246e3f1f620d33d98ee5463c621726b8080a031a80c76669969c6b2b75250c772b7e6bd96c74c9b4c9ab7f2b6aa082d310f41a0a6cd8a58c4c7642f25924ab0ea9c952aec850fd0410463fa3dbc9917b0c3e1e8a07753f936bbbf1f0430deea77beeedc3f77bddc8c863cdea1dce4b4f254c2659ba0a3a4bf85e7f3bf2a1a5385e2249356b83108162ad17bf2a8cf77ab57dbc7503da0376dba981a2ca2c1173c6d3bdf04b80e7446f8b1e964fefa4f0175e9f94e9d81a0ae11a26d017ccf8f7a9b0d4aff4639a24f038bf1ee5e10e41bf7aba4f45c7036a00b29d0a90e5f768e4bf4eb4b9efa8cd1ad41798d6bb08ccef06d53ac3f8e1a79a0c0850eb680dc0893f352571a86dc94f6eddf134f2c0a3f2737bb045a3f373eea80f869a03b51e6579cd86645444e321a80ba4795cbd7cda9712dae2aefba7cdbcf962d8eb846f8440180a027cd08827e6bf1e435832f4b2660107beb562314287b3fa534f3b189574c0ccaa0cf9747f04c15d40e52400ba1082794772b0c6a7bb8b554e52c6af3e19281186c").to_vec() + ).unwrap(), + account_storage_root: H256(hex!( + "27cd08827e6bf1e435832f4b2660107beb562314287b3fa534f3b189574c0cca" + )), + } + } + } + + // returns: (path, proof, value) + fn get_membership_proof() -> (String, Vec, Vec) { + ( + "clients/lcp-client-0/clientState".to_string(), + hex!("f90159f901118080a0143145e818eeff83817419a6632ea193fd1acaa4f791eb17282f623f38117f56a0e6ee0a993a7254ee9253d766ea005aec74eb1e11656961f0fb11323f4f91075580808080a01efae04adc2e970b4af3517581f41ce2ba4ff60492d33696c1e2a5ab70cb55bba03bac3f5124774e41fb6efdd7219530846f9f6441045c4666d2855c6598cfca00a020d7122ffc86cb37228940b5a9441e9fd272a3450245c9130ca3ab00bc1cd6ef80a0047f255205a0f2b0e7d29d490abf02bfb62c3ed201c338bc7f0088fa9c5d77eda069fecc766fcb2df04eb3a834b1f4ba134df2be114479e251d9cc9b6ba493077b80a094c3ed6a7ef63a6a67e46cc9876b9b1882eeba3d28e6d61bb15cdfb207d077e180f843a03e077f3dfd0489e70c68282ced0126c62fcef50acdcb7f57aa4552b87b456b11a1a05dc044e92e82db28c96fd98edd502949612b06e8da6dd74664a43a5ed857b298").to_vec(), + hex!("0a242f6962632e6c69676874636c69656e74732e6c63702e76312e436c69656e74537461746512ed010a208083673c69fe3f098ea79a799d9dbb99c39b4b4f17a1a79ef58bdf8ae86299951080f524220310fb012a1353575f48415244454e494e475f4e45454445442a1147524f55505f4f55545f4f465f44415445320e494e54454c2d53412d3030323139320e494e54454c2d53412d3030323839320e494e54454c2d53412d3030333334320e494e54454c2d53412d3030343737320e494e54454c2d53412d3030363134320e494e54454c2d53412d3030363135320e494e54454c2d53412d3030363137320e494e54454c2d53412d30303832383a14cb96f8d6c2d543102184d679d7829b39434e4eec48015001").to_vec() + ) + } + + // returns: (path, proof) + fn get_non_membership_proof() -> (String, Vec) { + ( + "clients/lcp-client-1/clientState".to_string(), + hex!("f90114f901118080a0143145e818eeff83817419a6632ea193fd1acaa4f791eb17282f623f38117f56a0e6ee0a993a7254ee9253d766ea005aec74eb1e11656961f0fb11323f4f91075580808080a01efae04adc2e970b4af3517581f41ce2ba4ff60492d33696c1e2a5ab70cb55bba03bac3f5124774e41fb6efdd7219530846f9f6441045c4666d2855c6598cfca00a020d7122ffc86cb37228940b5a9441e9fd272a3450245c9130ca3ab00bc1cd6ef80a0047f255205a0f2b0e7d29d490abf02bfb62c3ed201c338bc7f0088fa9c5d77eda069fecc766fcb2df04eb3a834b1f4ba134df2be114479e251d9cc9b6ba493077b80a094c3ed6a7ef63a6a67e46cc9876b9b1882eeba3d28e6d61bb15cdfb207d077e180").to_vec() + ) + } + + fn get_trusted_sync_committee( + lctx: &LightClientContext, + ctx: &MockContext, + scm: &MockSyncCommitteeManager, + signature_slot: U64, + ) -> TrustedSyncCommittee { + let client_state = ctx.client_state.as_ref().unwrap(); + let consensus_state = ctx + .consensus_states + .get(&client_state.latest_height().into()) + .unwrap(); + let store_period = consensus_state.current_period(lctx); + let target_period = compute_sync_committee_period_at_slot(lctx, signature_slot); + if store_period == target_period { + TrustedSyncCommittee { + height: client_state.latest_height(), + sync_committee: scm.get_committee(target_period.into()).to_committee(), + is_next: false, + } + } else if store_period + 1 == target_period { + TrustedSyncCommittee { + height: client_state.latest_height(), + sync_committee: scm.get_committee(target_period.into()).to_committee(), + is_next: true, + } + } else { + panic!( + "Invalid target: target_period={} store_period={} signature_slot={}", + target_period, store_period, signature_slot + ); + } + } + + fn convert_consensus_update( + consensus_update: &updates::ConsensusUpdateInfo, + ) -> ConsensusUpdateInfo { + ConsensusUpdateInfo { + attested_header: consensus_update.light_client_update.attested_header.clone(), + next_sync_committee: consensus_update + .light_client_update + .next_sync_committee + .clone(), + finalized_header: ( + consensus_update.finalized_beacon_header().clone(), + consensus_update.finalized_beacon_header_branch(), + ), + sync_aggregate: consensus_update.sync_aggregate().clone(), + signature_slot: consensus_update.signature_slot(), + finalized_execution_root: consensus_update.finalized_execution_root(), + finalized_execution_branch: consensus_update.finalized_execution_branch(), + } + } + + fn convert_execution_update( + execution_update: &updates::ExecutionUpdateInfo, + ) -> ExecutionUpdateInfo { + ExecutionUpdateInfo { + state_root: execution_update.state_root, + state_root_branch: execution_update.state_root_branch.clone(), + block_number: execution_update.block_number, + block_number_branch: execution_update.block_number_branch.clone(), + } + } + + 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/src/lib.rs b/src/lib.rs index fadd4e1..be2110f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -#![no_std] +#![cfg_attr(not(test), no_std)] #![allow(clippy::result_large_err)] #![allow(clippy::large_enum_variant)]