Skip to content

Commit

Permalink
Merge pull request #26 from datachainlab/update-verifier
Browse files Browse the repository at this point in the history
Update verifier

Signed-off-by: Jun Kimura <[email protected]>
  • Loading branch information
bluele authored Nov 12, 2024
2 parents 8243b63 + c7f469b commit 5022f3c
Show file tree
Hide file tree
Showing 7 changed files with 683 additions and 133 deletions.
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

0 comments on commit 5022f3c

Please sign in to comment.