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

Update verifier #26

Merged
merged 11 commits into from
Nov 12, 2024
6 changes: 3 additions & 3 deletions crates/ibc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "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 = "v0.1.4", 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"] }
199 changes: 148 additions & 51 deletions crates/ibc/src/client_state.rs

Large diffs are not rendered by default.

113 changes: 94 additions & 19 deletions crates/ibc/src/consensus_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}

Expand Down Expand Up @@ -224,36 +226,28 @@ impl<const SYNC_COMMITTEE_SIZE: usize> TrustedConsensusState<SYNC_COMMITTEE_SIZE
))
}
}

pub fn current_period<C: ChainContext>(&self, ctx: &C) -> SyncCommitteePeriod {
self.state.current_period(ctx)
}
}

impl<const SYNC_COMMITTEE_SIZE: usize> LightClientStoreReader<SYNC_COMMITTEE_SIZE>
for TrustedConsensusState<SYNC_COMMITTEE_SIZE>
{
fn get_sync_committee<CC: ChainContext>(
&self,
ctx: &CC,
period: SyncCommitteePeriod,
) -> Option<SyncCommittee<SYNC_COMMITTEE_SIZE>> {
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<C: ChainContext>(&self, ctx: &C) -> SyncCommitteePeriod {
self.state.current_period(ctx)
}

fn current_sync_committee(&self) -> Option<SyncCommittee<SYNC_COMMITTEE_SIZE>> {
self.current_sync_committee.clone()
}

fn next_sync_committee(&self) -> Option<SyncCommittee<SYNC_COMMITTEE_SIZE>> {
self.next_sync_committee.clone()
}

fn ensure_relevant_update<CC: ChainContext, C: ConsensusUpdate<SYNC_COMMITTEE_SIZE>>(
&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(
Expand All @@ -277,8 +271,80 @@ impl<const SYNC_COMMITTEE_SIZE: usize> From<TrustedConsensusState<SYNC_COMMITTEE
#[cfg(test)]
mod tests {
use super::*;
use ethereum_consensus::types::H256;
use ethereum_light_client_verifier::consensus::test_utils::MockSyncCommitteeManager;
use hex_literal::hex;
use time::macros::datetime;

#[test]
fn test_consensus_state_conversion() {
let consensus_state = ConsensusState {
slot: 1.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: PublicKey::try_from(hex!("a145063e1b5eda80fa55960296f2c4b2c021f75767318ea2572a9f7abb649010b746754ca7fc2ba57c1156881516a357").to_vec()).unwrap(),
next_sync_committee: PublicKey::try_from(hex!("a42dffb90d85cec7acfcb53be0e8792155d8f18c0dc9efc2a5587d5a0ba3e578df366fc3e2b743de6ecd3b53e345c266").to_vec()).unwrap(),
};
let res = consensus_state.validate();
assert!(res.is_ok(), "{:?}", res);
let any_consensus_state = IBCAny::from(consensus_state.clone());
let consensus_state2 = ConsensusState::try_from(any_consensus_state).unwrap();
assert_eq!(consensus_state, consensus_state2);
}

#[test]
fn test_trusted_consensus_state() {
let scm = MockSyncCommitteeManager::<32>::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() {
{
Expand All @@ -303,4 +369,13 @@ mod tests {
assert_eq!(it1, it2);
}
}

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)
}
}
12 changes: 8 additions & 4 deletions crates/ibc/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ pub enum Error {
Vec<String>,
),
/// 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:?}
Expand All @@ -40,8 +40,8 @@ pub enum Error {
AccountStorageRootMismatch(H256, H256, H256, String, Vec<String>),
/// invalid account storage root: {0:?}
InvalidAccountStorageRoot(Vec<u8>),
/// 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
Expand All @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -109,6 +111,8 @@ pub enum Error {
ProtoMissingFieldError(String),
/// unknown message type: `{0}`
UnknownMessageType(String),
/// cannot initialize frozen client
CannotInitializeFrozenClient,
}

impl Error {
Expand Down
115 changes: 96 additions & 19 deletions crates/ibc/src/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ use crate::types::{
ConsensusUpdateInfo, ExecutionUpdateInfo, TrustedSyncCommittee,
};
use bytes::Buf;
use ethereum_consensus::compute::compute_timestamp_at_slot;
use ethereum_consensus::context::ChainContext;
use ethereum_ibc_proto::ibc::lightclients::ethereum::v1::Header as RawHeader;
use ethereum_light_client_verifier::updates::ConsensusUpdate;
use ibc::core::ics02_client::error::ClientError;
use ibc::core::ics02_client::header::Header as Ics02Header;
use ibc::timestamp::Timestamp;
Expand Down Expand Up @@ -72,6 +75,36 @@ pub fn decode_header<const SYNC_COMMITTEE_SIZE: usize, B: Buf>(
RawHeader::decode(buf).map_err(Error::Decode)?.try_into()
}

impl<const SYNC_COMMITTEE_SIZE: usize> Header<SYNC_COMMITTEE_SIZE> {
pub fn validate<C: ChainContext>(&self, ctx: &C) -> Result<(), Error> {
self.trusted_sync_committee.sync_committee.validate()?;
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);
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_nanos,
header_timestamp_nanos,
));
}
Ok(())
}
}

impl<const SYNC_COMMITTEE_SIZE: usize> Ics02Header for Header<SYNC_COMMITTEE_SIZE> {
fn height(&self) -> ibc::Height {
ibc::Height::new(0, self.execution_update.block_number.into()).unwrap()
Expand Down Expand Up @@ -197,6 +230,57 @@ mod tests {
let dummy_execution_state_root = [1u8; 32].into();
let dummy_execution_block_number = 1;

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(
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);

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");
}

let update = gen_light_client_update_with_params::<32, _>(
&ctx,
base_signature_slot,
Expand All @@ -206,6 +290,7 @@ mod tests {
dummy_execution_block_number.into(),
current_sync_committee,
scm.get_committee(2),
true,
32,
);
let update = to_consensus_update_info(update);
Expand All @@ -218,26 +303,18 @@ mod tests {
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(),
timestamp: Timestamp::from_nanoseconds(
compute_timestamp_at_slot(&ctx, update.finalized_beacon_header().slot).0
* 1_000_000_000
+ 1,
)
.unwrap(),
};
let any = IBCAny::from(header.clone());
let res = Header::<32>::try_from(any);
assert!(res.is_err());
let res = header.validate(&ctx);
assert!(
res.is_err(),
"header validation should fail for wrong timestamp"
);
}

fn to_consensus_update_info<const SYNC_COMMITTEE_SIZE: usize>(
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 @@ -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, _>(
Expand All @@ -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);
Expand Down Expand Up @@ -285,6 +287,7 @@ mod tests {
dummy_execution_block_number.into(),
current_sync_committee,
scm.get_committee(2),
true,
32,
);

Expand Down
Loading