diff --git a/crates/ibc/Cargo.toml b/crates/ibc/Cargo.toml index 5baf155..a6111f1 100644 --- a/crates/ibc/Cargo.toml +++ b/crates/ibc/Cargo.toml @@ -16,10 +16,11 @@ 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.2.0", default-features = false } -ethereum-light-client-verifier = { git = "https://github.com/datachainlab/ethereum-light-client-rs", rev = "v0.2.0", default-features = false } +ethereum-consensus = { git = "https://github.com/yoshidan/ethereum-light-client-rs", branch = "feature/verify_block_hash", default-features = false } +ethereum-light-client-verifier = { git = "https://github.com/yoshidan/ethereum-light-client-rs", branch = "feature/verify_block_hash", 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.2.0", default-features = false, features = ["test-utils"] } +ethereum-light-client-verifier = { git = "https://github.com/yoshidan/ethereum-light-client-rs", branch = "feature/verify_block_hash", default-features = false, features = ["test-utils"] } + diff --git a/crates/ibc/src/client_state.rs b/crates/ibc/src/client_state.rs index 9f10da2..559f088 100644 --- a/crates/ibc/src/client_state.rs +++ b/crates/ibc/src/client_state.rs @@ -125,60 +125,12 @@ impl ClientState { state_root: H256, account_update: &AccountUpdateInfo, ) -> Result<(), Error> { - match self - .execution_verifier - .verify_account( - state_root, - &self.ibc_address, - account_update.account_proof.clone(), - ) - .map_err(|e| { - Error::MPTVerificationError( - e, - state_root, - hex::encode(self.ibc_address.0), - account_update - .account_proof - .iter() - .map(hex::encode) - .collect(), - ) - })? { - Some(account) => { - if account_update.account_storage_root == account.storage_root { - Ok(()) - } else { - Err(Error::AccountStorageRootMismatch( - account_update.account_storage_root, - account.storage_root, - state_root, - hex::encode(self.ibc_address.0), - account_update - .account_proof - .iter() - .map(hex::encode) - .collect(), - )) - } - } - None => { - if account_update.account_storage_root.is_zero() { - Ok(()) - } else { - Err(Error::AccountStorageRootMismatch( - account_update.account_storage_root, - H256::default(), - state_root, - hex::encode(self.ibc_address.0), - account_update - .account_proof - .iter() - .map(hex::encode) - .collect(), - )) - } - } - } + verify_account_storage( + &self.ibc_address, + &self.execution_verifier, + state_root, + account_update, + ) } pub fn verify_membership( @@ -770,6 +722,7 @@ impl TryFrom execution_payload_state_root_gindex: spec.execution_payload_state_root_gindex, execution_payload_block_number_gindex: spec .execution_payload_block_number_gindex, + execution_payload_block_hash_gindex: spec.execution_payload_block_hash_gindex, }) } else { Err(Error::proto_missing(&format!("forks[{}].spec", idx))) @@ -852,6 +805,7 @@ impl From> fo execution_payload_state_root_gindex: spec.execution_payload_state_root_gindex, execution_payload_block_number_gindex: spec .execution_payload_block_number_gindex, + execution_payload_block_hash_gindex: spec.execution_payload_block_hash_gindex, }), } } @@ -963,7 +917,7 @@ fn maybe_consensus_state( } } -fn trim_left_zero(value: &[u8]) -> &[u8] { +pub fn trim_left_zero(value: &[u8]) -> &[u8] { let mut pos = 0; for v in value { if *v != 0 { @@ -1026,6 +980,67 @@ fn verify_delay_passed( Ok(()) } +pub fn verify_account_storage( + ibc_address: &Address, + execution_verifier: &ExecutionVerifier, + state_root: H256, + account_update: &AccountUpdateInfo, +) -> Result<(), Error> { + match execution_verifier + .verify_account( + state_root, + ibc_address, + account_update.account_proof.clone(), + ) + .map_err(|e| { + Error::MPTVerificationError( + e, + state_root, + hex::encode(ibc_address.0), + account_update + .account_proof + .iter() + .map(hex::encode) + .collect(), + ) + })? { + Some(account) => { + if account_update.account_storage_root == account.storage_root { + Ok(()) + } else { + Err(Error::AccountStorageRootMismatch( + account_update.account_storage_root, + account.storage_root, + state_root, + hex::encode(ibc_address.0), + account_update + .account_proof + .iter() + .map(hex::encode) + .collect(), + )) + } + } + None => { + if account_update.account_storage_root.is_zero() { + Ok(()) + } else { + Err(Error::AccountStorageRootMismatch( + account_update.account_storage_root, + H256::default(), + state_root, + hex::encode(ibc_address.0), + account_update + .account_proof + .iter() + .map(hex::encode) + .collect(), + )) + } + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/ibc/src/header.rs b/crates/ibc/src/header.rs index 7042853..d710c6a 100644 --- a/crates/ibc/src/header.rs +++ b/crates/ibc/src/header.rs @@ -239,6 +239,7 @@ mod tests { let base_finalized_epoch = base_attested_slot / ctx.slots_per_epoch(); let dummy_execution_state_root = [1u8; 32].into(); let dummy_execution_block_number = 1; + let dummy_execution_block_hash = [1u8; 32].into(); for b in [false, true] { let (update, _) = gen_light_client_update_with_params::<32, _>( @@ -248,6 +249,7 @@ mod tests { base_finalized_epoch, dummy_execution_state_root, dummy_execution_block_number.into(), + dummy_execution_block_hash, current_sync_committee, scm.get_committee(2), b, @@ -304,6 +306,7 @@ mod tests { base_finalized_epoch, dummy_execution_state_root, dummy_execution_block_number.into(), + dummy_execution_block_hash, current_sync_committee, scm.get_committee(2), true, diff --git a/crates/ibc/src/misbehaviour.rs b/crates/ibc/src/misbehaviour.rs index a29fe78..afcdc46 100644 --- a/crates/ibc/src/misbehaviour.rs +++ b/crates/ibc/src/misbehaviour.rs @@ -244,6 +244,7 @@ mod tests { let base_finalized_epoch = base_attested_slot / ctx.slots_per_epoch(); let dummy_execution_state_root = [1u8; 32].into(); let dummy_execution_block_number = 1; + let dummy_execution_block_hash = [1u8; 32].into(); let (update_1, _) = gen_light_client_update_with_params::<32, _>( &ctx, @@ -252,6 +253,7 @@ mod tests { base_finalized_epoch, dummy_execution_state_root, dummy_execution_block_number.into(), + dummy_execution_block_hash, current_sync_committee, scm.get_committee(2), true, @@ -264,6 +266,7 @@ mod tests { base_finalized_epoch, dummy_execution_state_root, dummy_execution_block_number.into(), + dummy_execution_block_hash, current_sync_committee, scm.get_committee(3), true, @@ -295,6 +298,7 @@ mod tests { base_finalized_epoch, different_dummy_execution_state_root, dummy_execution_block_number.into(), + dummy_execution_block_hash, current_sync_committee, scm.get_committee(2), true, diff --git a/crates/ibc/src/types.rs b/crates/ibc/src/types.rs index 2fd8b0b..0df6bb2 100644 --- a/crates/ibc/src/types.rs +++ b/crates/ibc/src/types.rs @@ -81,6 +81,10 @@ pub struct ExecutionUpdateInfo { pub block_number: U64, /// Branch indicating the block number in the tree corresponding to the execution payload pub block_number_branch: Vec, + /// Block hash of the execution payload + pub block_hash: H256, + /// Branch indicating the block hash in the tree corresponding to the execution payload + pub block_hash_branch: Vec, } impl ExecutionUpdate for ExecutionUpdateInfo { @@ -99,6 +103,14 @@ impl ExecutionUpdate for ExecutionUpdateInfo { fn block_number_branch(&self) -> Vec { self.block_number_branch.clone() } + + fn block_hash(&self) -> H256 { + self.block_hash + } + + fn block_hash_branch(&self) -> Vec { + self.block_hash_branch.clone() + } } #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] @@ -139,25 +151,7 @@ impl TryFrom trusted_height.revision_number, trusted_height.revision_height, )?, - sync_committee: SyncCommittee { - pubkeys: Vector::::from_iter( - value - .sync_committee - .as_ref() - .ok_or(Error::proto_missing("sync_committee"))? - .pubkeys - .clone() - .into_iter() - .map(|pk| pk.try_into()) - .collect::, _>>()?, - ), - aggregate_pubkey: PublicKey::try_from( - value - .sync_committee - .ok_or(Error::proto_missing("sync_committee"))? - .aggregate_pubkey, - )?, - }, + sync_committee: convert_proto_to_sync_committee(value.sync_committee)?, is_next: value.is_next, }) } @@ -243,7 +237,7 @@ pub(crate) fn convert_header_to_proto(header: &BeaconBlockHeader) -> ProtoBeacon } } -pub(crate) fn convert_proto_to_execution_update( +pub fn convert_proto_to_execution_update( execution_update: ProtoExecutionUpdate, ) -> ExecutionUpdateInfo { ExecutionUpdateInfo { @@ -259,6 +253,12 @@ pub(crate) fn convert_proto_to_execution_update( .into_iter() .map(|n| H256::from_slice(&n)) .collect(), + block_hash: H256::from_slice(&execution_update.block_hash), + block_hash_branch: execution_update + .block_hash_branch + .into_iter() + .map(|n| H256::from_slice(&n)) + .collect(), } } @@ -278,6 +278,12 @@ pub(crate) fn convert_execution_update_to_proto( .into_iter() .map(|n| n.as_bytes().to_vec()) .collect(), + block_hash: execution_update.block_hash.as_bytes().into(), + block_hash_branch: execution_update + .block_hash_branch + .into_iter() + .map(|n| n.as_bytes().to_vec()) + .collect(), } } @@ -346,7 +352,7 @@ pub(crate) fn convert_consensus_update_to_proto( +pub fn convert_proto_to_consensus_update( consensus_update: ProtoConsensusUpdate, ) -> Result, Error> { let attested_header = convert_proto_to_header( @@ -381,24 +387,7 @@ pub(crate) fn convert_proto_to_consensus_update::from_iter( - consensus_update - .next_sync_committee - .clone() - .ok_or(Error::proto_missing("next_sync_committee"))? - .pubkeys - .into_iter() - .map(|pk| pk.try_into()) - .collect::, _>>()?, - ), - aggregate_pubkey: PublicKey::try_from( - consensus_update - .next_sync_committee - .ok_or(Error::proto_missing("next_sync_committee"))? - .aggregate_pubkey, - )?, - }, + convert_proto_to_sync_committee(consensus_update.next_sync_committee)?, decode_branch(consensus_update.next_sync_committee_branch), )) }, @@ -418,6 +407,28 @@ pub(crate) fn convert_proto_to_consensus_update( + sync_committee: Option, +) -> Result, Error> { + let sync_committee = SyncCommittee { + pubkeys: Vector::::from_iter( + sync_committee + .clone() + .ok_or(Error::proto_missing("next_sync_committee"))? + .pubkeys + .into_iter() + .map(|pk| pk.try_into()) + .collect::, _>>()?, + ), + aggregate_pubkey: PublicKey::try_from( + sync_committee + .ok_or(Error::proto_missing("next_sync_committee"))? + .aggregate_pubkey, + )?, + }; + Ok(sync_committee) +} + pub(crate) fn decode_branch(bz: Vec>) -> Vec { bz.into_iter().map(|b| H256::from_slice(&b)).collect() } diff --git a/crates/ibc/src/update.rs b/crates/ibc/src/update.rs index b59fa01..86cd1f3 100644 --- a/crates/ibc/src/update.rs +++ b/crates/ibc/src/update.rs @@ -128,6 +128,7 @@ mod tests { 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 dummy_execution_block_hash = [1u8; 32].into(); let client_state = ClientState::<{ ethereum_consensus::preset::minimal::PRESET.SYNC_COMMITTEE_SIZE }> { @@ -184,6 +185,7 @@ mod tests { base_finalized_epoch, dummy_execution_state_root, dummy_execution_block_number.into(), + dummy_execution_block_hash, current_sync_committee, scm.get_committee(base_store_period + 1), true, @@ -234,6 +236,7 @@ mod tests { base_finalized_epoch + ctx.epochs_per_sync_committee_period(), dummy_execution_state_root, dummy_execution_block_number.into(), + dummy_execution_block_hash, current_sync_committee, scm.get_committee(base_store_period + 2), true, @@ -285,6 +288,7 @@ mod tests { base_finalized_epoch + ctx.epochs_per_sync_committee_period(), dummy_execution_state_root, dummy_execution_block_number.into(), + dummy_execution_block_hash, current_sync_committee, scm.get_committee(base_store_period + 2), false, @@ -324,6 +328,7 @@ mod tests { base_finalized_epoch - ctx.epochs_per_sync_committee_period(), dummy_execution_state_root, dummy_execution_block_number.into(), + dummy_execution_block_hash, current_sync_committee, scm.get_committee(base_store_period), true, diff --git a/proto/definitions/ibc/lightclients/ethereum/v1/ethereum.proto b/proto/definitions/ibc/lightclients/ethereum/v1/ethereum.proto index e9f14f1..634110f 100644 --- a/proto/definitions/ibc/lightclients/ethereum/v1/ethereum.proto +++ b/proto/definitions/ibc/lightclients/ethereum/v1/ethereum.proto @@ -72,6 +72,7 @@ message ForkSpec { uint32 execution_payload_gindex = 4; uint32 execution_payload_state_root_gindex = 5; uint32 execution_payload_block_number_gindex = 6; + uint32 execution_payload_block_hash_gindex = 7; } message ConsensusUpdate { @@ -101,6 +102,8 @@ message ExecutionUpdate { repeated bytes state_root_branch = 2; uint64 block_number = 3; repeated bytes block_number_branch = 4; + bytes block_hash = 5; + repeated bytes block_hash_branch = 6; } message AccountUpdate { diff --git a/proto/src/prost/ibc.lightclients.ethereum.v1.rs b/proto/src/prost/ibc.lightclients.ethereum.v1.rs index 86669d5..eb129ac 100644 --- a/proto/src/prost/ibc.lightclients.ethereum.v1.rs +++ b/proto/src/prost/ibc.lightclients.ethereum.v1.rs @@ -120,6 +120,8 @@ pub struct ForkSpec { pub execution_payload_state_root_gindex: u32, #[prost(uint32, tag = "6")] pub execution_payload_block_number_gindex: u32, + #[prost(uint32, tag = "7")] + pub execution_payload_block_hash_gindex: u32, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -174,6 +176,10 @@ pub struct ExecutionUpdate { pub block_number: u64, #[prost(bytes = "vec", repeated, tag = "4")] pub block_number_branch: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>, + #[prost(bytes = "vec", tag = "5")] + pub block_hash: ::prost::alloc::vec::Vec, + #[prost(bytes = "vec", repeated, tag = "6")] + pub block_hash_branch: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)]