Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactoring and add verify_delay_passed checks #28

Merged
merged 6 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 90 additions & 12 deletions crates/ibc/src/client_state.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::commitment::{calculate_ibc_commitment_storage_key, decode_eip1184_rlp_proof};
use crate::commitment::{calculate_ibc_commitment_storage_location, decode_eip1184_rlp_proof};
use crate::consensus_state::{ConsensusState, TrustedConsensusState};
use crate::errors::Error;
use crate::header::Header;
Expand All @@ -22,6 +22,7 @@ use ibc::core::ics02_client::client_state::{ClientState as Ics2ClientState, Upda
use ibc::core::ics02_client::client_type::ClientType;
use ibc::core::ics02_client::consensus_state::ConsensusState as Ics02ConsensusState;
use ibc::core::ics02_client::error::ClientError;
use ibc::core::ics03_connection::connection::ConnectionEnd;
use ibc::core::ics24_host::identifier::{ChainId, ClientId};
use ibc::core::ics24_host::path::ClientConsensusStatePath;
use ibc::core::ics24_host::Path;
Expand All @@ -43,29 +44,42 @@ pub const ETHEREUM_ACCOUNT_STORAGE_ROOT_INDEX: usize = 2;

#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct ClientState<const SYNC_COMMITTEE_SIZE: usize> {
/// Chain parameters
// Verification parameters
/// `genesis_validators_root` of the target beacon chain's BeaconState
pub genesis_validators_root: Root,
/// https://github.com/ethereum/consensus-specs/blob/a09d0c321550c5411557674a981e2b444a1178c0/specs/altair/light-client/sync-protocol.md#misc
pub min_sync_committee_participants: U64,
/// `genesis_time` of the target beacon chain's BeaconState
pub genesis_time: U64,
/// fork parameters of the target beacon chain
pub fork_parameters: ForkParameters,
/// https://github.com/ethereum/consensus-specs/blob/a09d0c321550c5411557674a981e2b444a1178c0/configs/mainnet.yaml#L69
pub seconds_per_slot: U64,
/// https://github.com/ethereum/consensus-specs/blob/a09d0c321550c5411557674a981e2b444a1178c0/presets/mainnet/phase0.yaml#L36
pub slots_per_epoch: Slot,
/// https://github.com/ethereum/consensus-specs/blob/a09d0c321550c5411557674a981e2b444a1178c0/presets/mainnet/altair.yaml#L18
pub epochs_per_sync_committee_period: Epoch,

/// IBC Solidity parameters
/// An address of IBC contract on execution layer
pub ibc_address: Address,
/// The IBC contract's base storage location for storing commitments
/// https://github.com/hyperledger-labs/yui-ibc-solidity/blob/0e83dc7aadf71380dae6e346492e148685510663/docs/architecture.md#L46
pub ibc_commitments_slot: H256,

/// Light Client parameters
/// `trust_level` is threshold of sync committee participants to consider the attestation as valid. Highly recommended to be 2/3.
pub trust_level: Fraction,
/// `trusting_period` is the period in which the consensus state is considered trusted
pub trusting_period: Duration,
/// `max_clock_drift` defines how much new finalized header's time can drift into the future
pub max_clock_drift: Duration,

/// State
// State
/// The latest block number of the stored consensus state
pub latest_execution_block_number: U64,
/// `frozen_height` is the height at which the client is considered frozen. If `None`, the client is unfrozen.
pub frozen_height: Option<Height>,

/// Verifier
// Verifiers
#[serde(skip)]
pub consensus_verifier:
SyncProtocolVerifier<SYNC_COMMITTEE_SIZE, TrustedConsensusState<SYNC_COMMITTEE_SIZE>>,
Expand Down Expand Up @@ -188,7 +202,8 @@ impl<const SYNC_COMMITTEE_SIZE: usize> ClientState<SYNC_COMMITTEE_SIZE> {
),
});
}
let key = calculate_ibc_commitment_storage_key(&self.ibc_commitments_slot, path.clone());
let key =
calculate_ibc_commitment_storage_location(&self.ibc_commitments_slot, path.clone());
self.execution_verifier
.verify_membership(
root,
Expand Down Expand Up @@ -226,7 +241,8 @@ impl<const SYNC_COMMITTEE_SIZE: usize> ClientState<SYNC_COMMITTEE_SIZE> {
),
});
}
let key = calculate_ibc_commitment_storage_key(&self.ibc_commitments_slot, path.clone());
let key =
calculate_ibc_commitment_storage_location(&self.ibc_commitments_slot, path.clone());
self.execution_verifier
.verify_non_membership(root, key.as_bytes(), proof.clone())
.map_err(|e| ClientError::ClientSpecific {
Expand Down Expand Up @@ -424,6 +440,12 @@ impl<const SYNC_COMMITTEE_SIZE: usize> Ics2ClientState for ClientState<SYNC_COMM
}
let misbehaviour = Misbehaviour::<SYNC_COMMITTEE_SIZE>::try_from(misbehaviour)?;
misbehaviour.validate()?;
if misbehaviour.client_id != client_id {
return Err(
Error::UnexpectedClientIdInMisbehaviour(client_id, misbehaviour.client_id).into(),
);
}

let consensus_state = match maybe_consensus_state(
ctx,
&ClientConsensusStatePath::new(&client_id, &misbehaviour.trusted_sync_committee.height),
Expand Down Expand Up @@ -557,14 +579,15 @@ impl<const SYNC_COMMITTEE_SIZE: usize> Ics2ClientState for ClientState<SYNC_COMM

fn verify_packet_data(
&self,
_ctx: &dyn ibc::core::ValidationContext,
ctx: &dyn ibc::core::ValidationContext,
proof_height: ibc::Height,
connection_end: &ibc::core::ics03_connection::connection::ConnectionEnd,
proof: &ibc::core::ics23_commitment::commitment::CommitmentProofBytes,
root: &ibc::core::ics23_commitment::commitment::CommitmentRoot,
commitment_path: &ibc::core::ics24_host::path::CommitmentPath,
commitment: ibc::core::ics04_channel::commitment::PacketCommitment,
) -> Result<(), ClientError> {
verify_delay_passed(ctx, proof_height, connection_end)?;
self.verify_membership(
proof_height,
connection_end.counterparty().prefix(),
Expand All @@ -577,14 +600,15 @@ impl<const SYNC_COMMITTEE_SIZE: usize> Ics2ClientState for ClientState<SYNC_COMM

fn verify_packet_acknowledgement(
&self,
_ctx: &dyn ibc::core::ValidationContext,
ctx: &dyn ibc::core::ValidationContext,
proof_height: ibc::Height,
connection_end: &ibc::core::ics03_connection::connection::ConnectionEnd,
proof: &ibc::core::ics23_commitment::commitment::CommitmentProofBytes,
root: &ibc::core::ics23_commitment::commitment::CommitmentRoot,
ack_path: &ibc::core::ics24_host::path::AckPath,
ack: ibc::core::ics04_channel::commitment::AcknowledgementCommitment,
) -> Result<(), ClientError> {
verify_delay_passed(ctx, proof_height, connection_end)?;
self.verify_membership(
proof_height,
connection_end.counterparty().prefix(),
Expand All @@ -597,14 +621,15 @@ impl<const SYNC_COMMITTEE_SIZE: usize> Ics2ClientState for ClientState<SYNC_COMM

fn verify_next_sequence_recv(
&self,
_ctx: &dyn ibc::core::ValidationContext,
ctx: &dyn ibc::core::ValidationContext,
proof_height: ibc::Height,
connection_end: &ibc::core::ics03_connection::connection::ConnectionEnd,
proof: &ibc::core::ics23_commitment::commitment::CommitmentProofBytes,
root: &ibc::core::ics23_commitment::commitment::CommitmentRoot,
seq_recv_path: &ibc::core::ics24_host::path::SeqRecvPath,
sequence: ibc::core::ics04_channel::packet::Sequence,
) -> Result<(), ClientError> {
verify_delay_passed(ctx, proof_height, connection_end)?;
let mut seq_bytes = Vec::new();
u64::from(sequence)
.encode(&mut seq_bytes)
Expand All @@ -622,13 +647,14 @@ impl<const SYNC_COMMITTEE_SIZE: usize> Ics2ClientState for ClientState<SYNC_COMM

fn verify_packet_receipt_absence(
&self,
_ctx: &dyn ibc::core::ValidationContext,
ctx: &dyn ibc::core::ValidationContext,
proof_height: ibc::Height,
connection_end: &ibc::core::ics03_connection::connection::ConnectionEnd,
proof: &ibc::core::ics23_commitment::commitment::CommitmentProofBytes,
root: &ibc::core::ics23_commitment::commitment::CommitmentRoot,
receipt_path: &ibc::core::ics24_host::path::ReceiptPath,
) -> Result<(), ClientError> {
verify_delay_passed(ctx, proof_height, connection_end)?;
self.verify_non_membership(
proof_height,
connection_end.counterparty().prefix(),
Expand Down Expand Up @@ -937,6 +963,58 @@ fn trim_left_zero(value: &[u8]) -> &[u8] {
&value[pos..]
}

// A copy from https://github.com/cosmos/ibc-rs/blob/eea4f0e7a1887f2f1cb18a550d08bb805a08240a/crates/ibc/src/clients/ics07_tendermint/client_state.rs#L1031
fn verify_delay_passed(
ctx: &dyn ValidationContext,
height: Height,
connection_end: &ConnectionEnd,
) -> Result<(), ClientError> {
let current_timestamp = ctx.host_timestamp().map_err(|e| ClientError::Other {
description: e.to_string(),
})?;
let current_height = ctx.host_height().map_err(|e| ClientError::Other {
description: e.to_string(),
})?;

let client_id = connection_end.client_id();
let processed_time =
ctx.client_update_time(client_id, &height)
.map_err(|_| Error::ProcessedTimeNotFound {
client_id: client_id.clone(),
height,
})?;
let processed_height = ctx.client_update_height(client_id, &height).map_err(|_| {
Error::ProcessedHeightNotFound {
client_id: client_id.clone(),
height,
}
})?;

let delay_period_time = connection_end.delay_period();
let delay_period_height = ctx.block_delay(&delay_period_time);

let earliest_time =
(processed_time + delay_period_time).map_err(Error::TimestampOverflowError)?;
if !(current_timestamp == earliest_time || current_timestamp.after(&earliest_time)) {
return Err(Error::NotEnoughTimeElapsed {
current_timestamp,
earliest_time,
}
.into());
}

let earliest_height = processed_height.add(delay_period_height);
if current_height < earliest_height {
return Err(Error::NotEnoughBlocksElapsed {
current_height,
earliest_height,
}
.into());
}

Ok(())
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
29 changes: 5 additions & 24 deletions crates/ibc/src/commitment.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
use crate::errors::Error;
use crate::internal_prelude::*;
use ethereum_consensus::types::{Address, H256};
use ethereum_consensus::types::H256;
use ibc::core::ics24_host::Path;
use rlp::Rlp;
use tiny_keccak::{Hasher, Keccak};

pub fn calculate_account_path(ibc_address: &Address) -> H256 {
keccak_256(&ibc_address.0).into()
}

pub fn calculate_ibc_commitment_storage_key(ibc_commitments_slot: &H256, path: Path) -> H256 {
/// Calculate the storage location for the commitment stored in the IBC contract
///
/// The spec is here: https://github.com/hyperledger-labs/yui-ibc-solidity/blob/0e83dc7aadf71380dae6e346492e148685510663/docs/architecture.md#L46
pub fn calculate_ibc_commitment_storage_location(ibc_commitments_slot: &H256, path: Path) -> H256 {
keccak_256(
&[
&keccak_256(&path.into_bytes()),
Expand Down Expand Up @@ -37,24 +36,6 @@ pub fn decode_eip1184_rlp_proof(proof: Vec<u8>) -> Result<Vec<Vec<u8>>, Error> {
}
}

pub fn extract_storage_root_from_account(account_rlp: &[u8]) -> Result<H256, Error> {
let r = Rlp::new(account_rlp);
if !r.is_list() {
let items: Vec<Vec<u8>> = r.as_list()?;
if items.len() != 4 {
Err(Error::InvalidProofFormatError(
"proof must be rlp list".into(),
))
} else {
Ok(H256::from_slice(items.get(2).unwrap()))
}
} else {
Err(Error::InvalidProofFormatError(
"proof must be rlp list".into(),
))
}
}

fn keccak_256(input: &[u8]) -> [u8; 32] {
let mut out = [0u8; 32];
let mut k = Keccak::v256();
Expand Down
32 changes: 21 additions & 11 deletions crates/ibc/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ use ethereum_consensus::{
types::{H256, U64},
};
use ibc::{
core::{ics02_client::error::ClientError, ics24_host::error::ValidationError, ContextError},
core::{
ics02_client::error::ClientError,
ics24_host::{error::ValidationError, identifier::ClientId},
ContextError,
},
timestamp::{ParseTimestampError, Timestamp, TimestampOverflowError},
Height,
};
Expand All @@ -34,12 +38,8 @@ pub enum Error {
InvalidNextSyncCommitteeKeys(PublicKey, PublicKey),
/// invalid proof format error: {0}
InvalidProofFormatError(String),
/// rlp decode error: {0}
RLPDecodeError(rlp::DecoderError),
/// account storage root mismatch: expected={0} actual={1} state_root={2} address={3} account_proof={4:?}
AccountStorageRootMismatch(H256, H256, H256, String, Vec<String>),
/// invalid account storage root: {0:?}
InvalidAccountStorageRoot(Vec<u8>),
/// 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}
Expand Down Expand Up @@ -115,6 +115,22 @@ pub enum Error {
UnknownMessageType(String),
/// cannot initialize frozen client
CannotInitializeFrozenClient,
/// unexpected client ID in misbehaviour: expected={0} got={1}
UnexpectedClientIdInMisbehaviour(ClientId, ClientId),
/// Processed time for the client `{client_id}` at height `{height}` not found
ProcessedTimeNotFound { client_id: ClientId, height: Height },
/// Processed height for the client `{client_id}` at height `{height}` not found
ProcessedHeightNotFound { client_id: ClientId, height: Height },
/// not enough time elapsed, current timestamp `{current_timestamp}` is still less than earliest acceptable timestamp `{earliest_time}`
NotEnoughTimeElapsed {
current_timestamp: Timestamp,
earliest_time: Timestamp,
},
/// not enough blocks elapsed, current height `{current_height}` is still less than earliest acceptable height `{earliest_height}`
NotEnoughBlocksElapsed {
current_height: Height,
earliest_height: Height,
},
}

impl Error {
Expand All @@ -137,12 +153,6 @@ impl From<Error> for ContextError {
}
}

impl From<rlp::DecoderError> for Error {
fn from(value: rlp::DecoderError) -> Self {
Error::RLPDecodeError(value)
}
}

impl From<ethereum_consensus::errors::Error> for Error {
fn from(value: ethereum_consensus::errors::Error) -> Self {
Error::EthereumConsensusError(value)
Expand Down
5 changes: 5 additions & 0 deletions crates/ibc/src/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,15 @@ impl<const SYNC_COMMITTEE_SIZE: usize> From<ClientMessage<SYNC_COMMITTEE_SIZE>>

#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct Header<const SYNC_COMMITTEE_SIZE: usize> {
/// trusted sync committee corresponding to the period of the signature slot of the `consensus_update`
pub trusted_sync_committee: TrustedSyncCommittee<SYNC_COMMITTEE_SIZE>,
/// consensus update attested by the `trusted_sync_committee`
pub consensus_update: ConsensusUpdateInfo<SYNC_COMMITTEE_SIZE>,
/// execution update based on the `consensus_update.finalized_header`
pub execution_update: ExecutionUpdateInfo,
/// account update based on the `execution_update.state_root`
pub account_update: AccountUpdateInfo,
/// timestamp of the `consensus_update.finalized_header`
pub timestamp: Timestamp,
}

Expand Down
3 changes: 3 additions & 0 deletions crates/ibc/src/misbehaviour.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,11 @@ pub const ETHEREUM_NEXT_SYNC_COMMITTEE_MISBEHAVIOUR_TYPE_URL: &str =

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Misbehaviour<const SYNC_COMMITTEE_SIZE: usize> {
/// The client identifier
pub client_id: ClientId,
/// The sync committee related to the misbehaviour
pub trusted_sync_committee: TrustedSyncCommittee<SYNC_COMMITTEE_SIZE>,
/// The misbehaviour data
pub data: MisbehaviourData<SYNC_COMMITTEE_SIZE, ConsensusUpdateInfo<SYNC_COMMITTEE_SIZE>>,
}

Expand Down