From 3e72104378c88bae52305290e6dd9765b994b678 Mon Sep 17 00:00:00 2001 From: Naohiro Yoshida Date: Wed, 27 Nov 2024 12:28:35 +0900 Subject: [PATCH] BELC5 Signed-off-by: Naohiro Yoshida --- .github/workflows/ci.yaml | 8 +++ light-client/build.rs | 18 ++++++- light-client/src/client.rs | 76 ++++++++++++++++++++++++++- light-client/src/client_state.rs | 79 ++++++++++++++++++++++++++++- light-client/src/errors.rs | 8 +++ light-client/src/header/hardfork.rs | 2 + light-client/src/header/mod.rs | 5 ++ 7 files changed, 193 insertions(+), 3 deletions(-) create mode 100644 light-client/src/header/hardfork.rs diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index de3b7f8..45d0b19 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -41,3 +41,11 @@ jobs: with: command: test args: --release --features=std --manifest-path light-client/Cargo.toml + - uses: actions-rs/cargo@v1 + name: unit-test-dev-test + with: + command: test + args: --release --features=dev --manifest-path light-client/Cargo.toml --lib test::dev_test + env: + MINIMUM_TIMESTAMP_SUPPORTED: 1731495592 + MINIMUM_HEIGHT_SUPPORTED: 100 diff --git a/light-client/build.rs b/light-client/build.rs index feae427..f326f41 100644 --- a/light-client/build.rs +++ b/light-client/build.rs @@ -8,8 +8,24 @@ fn main() { writeln!( file, "pub const BLOCKS_PER_EPOCH: u64 = {};", - blocks_per_epoch + blocks_per_epoch, ) .unwrap(); } + + { + use std::io::Write; + let mut file = std::fs::File::create("src/header/hardfork.rs").unwrap(); + let minimum_time_stamp_supported = + std::env::var("MINIMUM_TIMESTAMP_SUPPORTED").unwrap_or_else(|_| "0".to_string()); + let minimum_height_supported = + std::env::var("MINIMUM_HEIGHT_SUPPORTED").unwrap_or_else(|_| "0".to_string()); + writeln!( + file, + "pub const MINIMUM_TIMESTAMP_SUPPORTED: u64 = {};\npub const MINIMUM_HEIGHT_SUPPORTED: u64 = {};", + minimum_time_stamp_supported, + minimum_height_supported + ) + .unwrap(); + } } diff --git a/light-client/src/client.rs b/light-client/src/client.rs index e64c343..a3ea2b1 100644 --- a/light-client/src/client.rs +++ b/light-client/src/client.rs @@ -19,7 +19,7 @@ use crate::commitment::{ }; use crate::consensus_state::ConsensusState; use crate::errors::{ClientError, Error}; - +use crate::header::hardfork::{MINIMUM_HEIGHT_SUPPORTED, MINIMUM_TIMESTAMP_SUPPORTED}; use crate::header::Header; use crate::message::ClientMessage; use crate::misbehaviour::Misbehaviour; @@ -165,6 +165,14 @@ impl InnerLightClient { let height = client_state.latest_height; let timestamp = consensus_state.timestamp; + #[allow(clippy::absurd_extreme_comparisons)] + if timestamp.as_unix_timestamp_secs() < MINIMUM_TIMESTAMP_SUPPORTED { + return Err(Error::UnsupportedMinimumTimestamp(timestamp)); + } + #[allow(clippy::absurd_extreme_comparisons)] + if height.revision_height() < MINIMUM_HEIGHT_SUPPORTED { + return Err(Error::UnsupportedMinimumHeight(height)); + } if height.revision_height() == 0 { return Err(Error::UnexpectedRevisionHeight(height.revision_height())); } @@ -1143,4 +1151,70 @@ mod test { fn assert_err(err: light_client::Error, contains: &str) { assert!(format!("{:?}", err).contains(contains), "{}", err); } + + #[cfg(feature = "dev")] + mod dev_test { + use crate::client::test::MockClientReader; + use crate::client::ParliaLightClient; + use crate::client_state::ClientState; + use crate::consensus_state::ConsensusState; + use crate::header::hardfork::{MINIMUM_HEIGHT_SUPPORTED, MINIMUM_TIMESTAMP_SUPPORTED}; + use crate::misc::{new_height, new_timestamp}; + use hex_literal::hex; + use light_client::{types::Any, LightClient}; + use std::collections::BTreeMap; + + #[test] + fn test_supported_timestamp() { + let client_state = hex!("0a272f6962632e6c69676874636c69656e74732e7061726c69612e76312e436c69656e745374617465124d08381214151f3951fa218cac426edfe078fa9e5c6dcea5001a2000000000000000000000000000000000000000000000000000000000000000002205109b9ea90f2a040880a305320410c0843d").to_vec(); + let consensus_state = hex!("0a2a2f6962632e6c69676874636c69656e74732e7061726c69612e76312e436f6e73656e7375735374617465126c0a2056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b42110de82d5a8061a209c59cf0b5717cb6e2bd8620b7f3481605c8abcd45636bdf45c86db06338f0c5e22207a1dede35f5c835fecdc768324928cd0d9d9161e8529e1ba1e60451f3a9d088a").to_vec(); + let client = ParliaLightClient::default(); + let mock_consensus_state = BTreeMap::new(); + let ctx = MockClientReader { + client_state: None, + consensus_state: mock_consensus_state, + }; + let any_client_state: Any = client_state.try_into().unwrap(); + let any_consensus_state: Any = consensus_state.try_into().unwrap(); + let err = client + .create_client(&ctx, any_client_state.clone(), any_consensus_state.clone()) + .unwrap_err(); + assert!( + format!("{:?}", err).contains("UnsupportedMinimumTimestamp"), + "{}", + err + ); + } + + #[test] + fn test_supported_height() { + let client_state = hex!("0a272f6962632e6c69676874636c69656e74732e7061726c69612e76312e436c69656e745374617465124d08381214151f3951fa218cac426edfe078fa9e5c6dcea5001a2000000000000000000000000000000000000000000000000000000000000000002205109b9ea90f2a040880a305320410c0843d").to_vec(); + let consensus_state = hex!("0a2a2f6962632e6c69676874636c69656e74732e7061726c69612e76312e436f6e73656e7375735374617465126c0a2056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b42110de82d5a8061a209c59cf0b5717cb6e2bd8620b7f3481605c8abcd45636bdf45c86db06338f0c5e22207a1dede35f5c835fecdc768324928cd0d9d9161e8529e1ba1e60451f3a9d088a").to_vec(); + let client = ParliaLightClient::default(); + let mock_consensus_state = BTreeMap::new(); + let ctx = MockClientReader { + client_state: None, + consensus_state: mock_consensus_state, + }; + let any_client_state: Any = client_state.try_into().unwrap(); + let any_consensus_state: Any = consensus_state.try_into().unwrap(); + + let mut client_state: ClientState = any_client_state.try_into().unwrap(); + client_state.latest_height = new_height(0, MINIMUM_HEIGHT_SUPPORTED - 1); + let mut consensus_state: ConsensusState = any_consensus_state.try_into().unwrap(); + consensus_state.timestamp = new_timestamp(MINIMUM_TIMESTAMP_SUPPORTED).unwrap(); + + let any_client_state: Any = client_state.try_into().unwrap(); + let any_consensus_state: Any = consensus_state.try_into().unwrap(); + + let err = client + .create_client(&ctx, any_client_state.clone(), any_consensus_state.clone()) + .unwrap_err(); + assert!( + format!("{:?}", err).contains("UnsupportedMinimumHeight"), + "{}", + err + ); + } + } } diff --git a/light-client/src/client_state.rs b/light-client/src/client_state.rs index 28880b9..60fe11f 100644 --- a/light-client/src/client_state.rs +++ b/light-client/src/client_state.rs @@ -11,6 +11,7 @@ use parlia_ibc_proto::ibc::lightclients::parlia::v1::ClientState as RawClientSta use crate::commitment::resolve_account; use crate::consensus_state::ConsensusState; use crate::errors::Error; +use crate::header::hardfork::{MINIMUM_HEIGHT_SUPPORTED, MINIMUM_TIMESTAMP_SUPPORTED}; use crate::header::Header; use crate::misbehaviour::Misbehaviour; use crate::misc::{new_height, Address, ChainId, Hash}; @@ -97,12 +98,23 @@ impl ClientState { } fn check_header(&self, now: Time, cs: &ConsensusState, header: &Header) -> Result<(), Error> { + // Ensure header has supported timestamp + let ts = header.timestamp()?; + + #[allow(clippy::absurd_extreme_comparisons)] + if ts.as_unix_timestamp_secs() < MINIMUM_TIMESTAMP_SUPPORTED { + return Err(Error::UnsupportedMinimumTimestamp(ts)); + } + #[allow(clippy::absurd_extreme_comparisons)] + if header.height().revision_height() < MINIMUM_HEIGHT_SUPPORTED { + return Err(Error::UnsupportedMinimumHeight(header.height())); + } // Ensure last consensus state is within the trusting period validate_within_trusting_period( now, self.trusting_period, self.max_clock_drift, - header.timestamp()?, + ts, cs.timestamp, )?; @@ -693,4 +705,69 @@ mod test { panic!("expected error"); } } + #[cfg(feature = "dev")] + mod dev_test { + use crate::client_state::ClientState; + use crate::consensus_state::ConsensusState; + use crate::errors::Error; + use crate::fixture::localnet; + use crate::header::eth_headers::ETHHeaders; + use crate::header::hardfork::{MINIMUM_HEIGHT_SUPPORTED, MINIMUM_TIMESTAMP_SUPPORTED}; + use crate::header::Header; + use crate::misc::new_timestamp; + use parlia_ibc_proto::ibc::core::client::v1::Height; + + #[test] + fn test_supported_timestamp() { + let header = Header::new( + vec![1], + ETHHeaders { + target: localnet().previous_epoch_header(), + all: vec![], + }, + Height::default(), + localnet().previous_epoch_header().epoch.unwrap(), + localnet().epoch_header().epoch.unwrap(), + ); + let cs = ClientState::default(); + let cons_state = ConsensusState::default(); + let err = cs + .check_header(new_timestamp(0).unwrap(), &cons_state, &header) + .unwrap_err(); + match err { + Error::UnsupportedMinimumTimestamp(e1) => { + assert_eq!(e1, header.timestamp().unwrap()); + } + err => unreachable!("{:?}", err), + } + } + + #[test] + fn test_supported_height() { + let mut header = Header::new( + vec![1], + ETHHeaders { + target: localnet().previous_epoch_header(), + all: vec![], + }, + Height::default(), + localnet().previous_epoch_header().epoch.unwrap(), + localnet().epoch_header().epoch.unwrap(), + ); + header.eth_header_mut().target.timestamp = MINIMUM_TIMESTAMP_SUPPORTED; + header.eth_header_mut().target.number = MINIMUM_HEIGHT_SUPPORTED - 1; + + let cs = ClientState::default(); + let cons_state = ConsensusState::default(); + let err = cs + .check_header(new_timestamp(0).unwrap(), &cons_state, &header) + .unwrap_err(); + match err { + Error::UnsupportedMinimumHeight(e1) => { + assert_eq!(e1, header.height()); + } + err => unreachable!("{:?}", err), + } + } + } } diff --git a/light-client/src/errors.rs b/light-client/src/errors.rs index dfa7371..45bb391 100644 --- a/light-client/src/errors.rs +++ b/light-client/src/errors.rs @@ -51,6 +51,8 @@ pub enum Error { UnexpectedValidatorsHashSize(Vec), // Header error + UnsupportedMinimumTimestamp(Time), + UnsupportedMinimumHeight(Height), MissingPreviousValidators(BlockNumber), MissingCurrentValidators(BlockNumber), OutOfTrustingPeriod(Time, Time), @@ -377,6 +379,12 @@ impl core::fmt::Display for Error { e1, e2, e3, e4 ) } + Error::UnsupportedMinimumTimestamp(e1) => { + write!(f, "UnsupportedMinimumTimestamp : {:?}", e1) + } + Error::UnsupportedMinimumHeight(e1) => { + write!(f, "UnsupportedMinimumHeight : {:?}", e1) + } Error::UnexpectedRevisionHeight(e1) => { write!(f, "UnexpectedRevisionHeight : {}", e1) } diff --git a/light-client/src/header/hardfork.rs b/light-client/src/header/hardfork.rs new file mode 100644 index 0000000..2b2db29 --- /dev/null +++ b/light-client/src/header/hardfork.rs @@ -0,0 +1,2 @@ +pub const MINIMUM_TIMESTAMP_SUPPORTED: u64 = 0; +pub const MINIMUM_HEIGHT_SUPPORTED: u64 = 0; diff --git a/light-client/src/header/mod.rs b/light-client/src/header/mod.rs index f87c04f..90b826b 100644 --- a/light-client/src/header/mod.rs +++ b/light-client/src/header/mod.rs @@ -29,6 +29,7 @@ pub mod validator_set; pub mod vote_attestation; pub mod epoch; +pub mod hardfork; #[derive(Clone, Debug, PartialEq)] pub struct Header { @@ -273,6 +274,10 @@ pub(crate) mod test { &self.headers } + pub(crate) fn eth_header_mut(&mut self) -> &mut ETHHeaders { + &mut self.headers + } + pub(crate) fn new( account_proof: Vec, headers: ETHHeaders,