From 8399ccdf3d2e303eea728aa01cfecf2bcd7bb956 Mon Sep 17 00:00:00 2001 From: Naohiro Yoshida Date: Wed, 13 Nov 2024 18:06:55 +0900 Subject: [PATCH 1/4] BELC3 Signed-off-by: Naohiro Yoshida --- light-client/src/client_state.rs | 20 ++++++++++++++++++++ light-client/src/errors.rs | 4 ++++ 2 files changed, 24 insertions(+) diff --git a/light-client/src/client_state.rs b/light-client/src/client_state.rs index ee00f71..28880b9 100644 --- a/light-client/src/client_state.rs +++ b/light-client/src/client_state.rs @@ -159,6 +159,13 @@ impl TryFrom for ClientState { let chain_id = ChainId::new(value.chain_id); + if chain_id.version() != raw_latest_height.revision_number { + return Err(Error::UnexpectedLatestHeightRevision( + chain_id.version(), + raw_latest_height.revision_number, + )); + } + let latest_height = new_height( raw_latest_height.revision_number, raw_latest_height.revision_height, @@ -489,6 +496,19 @@ mod test { err => unreachable!("{:?}", err), } + cs.latest_height = Some(Height { + revision_number: 1, + revision_height: 0, + }); + let err = ClientState::try_from(cs.clone()).unwrap_err(); + match err { + Error::UnexpectedLatestHeightRevision(e1, e2) => { + assert_eq!(e1, 0); + assert_eq!(e2, 1); + } + err => unreachable!("{:?}", err), + } + cs.latest_height = Some(Height::default()); let err = ClientState::try_from(cs.clone()).unwrap_err(); match err { diff --git a/light-client/src/errors.rs b/light-client/src/errors.rs index 7c152ce..a90d2a5 100644 --- a/light-client/src/errors.rs +++ b/light-client/src/errors.rs @@ -60,6 +60,7 @@ pub enum Error { UnexpectedTrustedHeight(BlockNumber, BlockNumber), EmptyHeader, UnexpectedHeaderRevision(u64, u64), + UnexpectedLatestHeightRevision(u64, u64), UnexpectedSignature(BlockNumber, signature::Error), MissingVanityInExtraData(BlockNumber, usize, usize), MissingSignatureInExtraData(BlockNumber, usize, usize), @@ -163,6 +164,9 @@ impl core::fmt::Display for Error { Error::UnexpectedHeaderRevision(e1, e2) => { write!(f, "UnexpectedHeaderRevision: {} {}", e1, e2) } + Error::UnexpectedLatestHeightRevision(e1, e2) => { + write!(f, "UnexpectedLatestHeightRevision: {} {}", e1, e2) + } Error::UnexpectedSignature(e1, e2) => write!(f, "UnexpectedSignature: {} {}", e1, e2), Error::MissingVanityInExtraData(e1, e2, e3) => { write!(f, "MissingVanityInExtraData: {} {} {}", e1, e2, e3) From b52708059cafa241233de80bddd01ba943d39d84 Mon Sep 17 00:00:00 2001 From: Naohiro Yoshida Date: Wed, 13 Nov 2024 18:32:12 +0900 Subject: [PATCH 2/4] BELC6 Signed-off-by: Naohiro Yoshida --- light-client/src/client.rs | 24 ++++++++++++++++++++++++ light-client/src/errors.rs | 4 ++++ 2 files changed, 28 insertions(+) diff --git a/light-client/src/client.rs b/light-client/src/client.rs index 4d064a7..e64c343 100644 --- a/light-client/src/client.rs +++ b/light-client/src/client.rs @@ -165,6 +165,10 @@ impl InnerLightClient { let height = client_state.latest_height; let timestamp = consensus_state.timestamp; + if height.revision_height() == 0 { + return Err(Error::UnexpectedRevisionHeight(height.revision_height())); + } + Ok(CreateClientResult { height, message: UpdateStateProxyMessage { @@ -590,6 +594,26 @@ mod test { _ => unreachable!("invalid commitment"), } } + #[test] + fn test_error_create_client() { + 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 mut any_client_state: Any = client_state.try_into().unwrap(); + let mut client_state = ClientState::try_from(any_client_state.clone()).unwrap(); + client_state.latest_height = Height::new(0, 0); + any_client_state = client_state.try_into().unwrap(); + let any_consensus_state: Any = consensus_state.try_into().unwrap(); + let result = client + .create_client(&ctx, any_client_state.clone(), any_consensus_state.clone()) + .unwrap_err(); + assert_err(result, "UnexpectedRevisionHeight"); + } #[rstest] #[case::localnet(localnet())] diff --git a/light-client/src/errors.rs b/light-client/src/errors.rs index 7c152ce..9b0d653 100644 --- a/light-client/src/errors.rs +++ b/light-client/src/errors.rs @@ -32,6 +32,7 @@ pub enum Error { UnexpectedCommitmentSlot(Vec), ClientFrozen(ClientId), UnexpectedProofHeight(Height, Height), + UnexpectedRevisionHeight(u64), // ConsensusState error AccountNotFound(Address), @@ -372,6 +373,9 @@ impl core::fmt::Display for Error { e1, e2, e3, e4 ) } + Error::UnexpectedRevisionHeight(e1) => { + write!(f, "UnexpectedRevisionHeight : {}", e1) + } } } } From 34d6fa2805c8f0c8d7d20a66c8ae4f7a2985915c Mon Sep 17 00:00:00 2001 From: Naohiro Yoshida Date: Wed, 13 Nov 2024 18:49:46 +0900 Subject: [PATCH 3/4] BELC5 Signed-off-by: Naohiro Yoshida --- .github/workflows/ci.yaml | 8 +++ light-client/build.rs | 18 ++++++- light-client/src/client.rs | 77 +++++++++++++++++++++++++++- 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, 194 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..7012598 100644 --- a/light-client/build.rs +++ b/light-client/build.rs @@ -8,7 +8,23 @@ 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 4d064a7..c4d948b 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,15 @@ 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)); + } + Ok(CreateClientResult { height, message: UpdateStateProxyMessage { @@ -1119,4 +1128,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 ee00f71..a166b17 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, )?; @@ -673,4 +685,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 7c152ce..3baf544 100644 --- a/light-client/src/errors.rs +++ b/light-client/src/errors.rs @@ -50,6 +50,8 @@ pub enum Error { UnexpectedValidatorsHashSize(Vec), // Header error + UnsupportedMinimumTimestamp(Time), + UnsupportedMinimumHeight(Height), MissingPreviousValidators(BlockNumber), MissingCurrentValidators(BlockNumber), OutOfTrustingPeriod(Time, Time), @@ -372,6 +374,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) + } } } } 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, From bd83fed5b200107c299c03142ccbf168320a4c6c Mon Sep 17 00:00:00 2001 From: Naohiro Yoshida Date: Tue, 26 Nov 2024 20:19:48 +0900 Subject: [PATCH 4/4] BELC1 (#68) * BELC1 Signed-off-by: Naohiro Yoshida * fix comment Signed-off-by: Naohiro Yoshida --------- Signed-off-by: Naohiro Yoshida Co-authored-by: Naohiro Yoshida --- light-client/src/client.rs | 16 +- light-client/src/errors.rs | 18 +- light-client/src/header/epoch.rs | 116 ++++++------ light-client/src/header/eth_headers.rs | 189 +++++++++++++++----- light-client/src/header/mod.rs | 6 +- light-client/src/header/vote_attestation.rs | 10 +- 6 files changed, 228 insertions(+), 127 deletions(-) diff --git a/light-client/src/client.rs b/light-client/src/client.rs index a3ea2b1..a85586f 100644 --- a/light-client/src/client.rs +++ b/light-client/src/client.rs @@ -571,7 +571,7 @@ mod test { fn test_success_create_client() { let client_state = hex!("0a272f6962632e6c69676874636c69656e74732e7061726c69612e76312e436c69656e745374617465124d08381214151f3951fa218cac426edfe078fa9e5c6dcea5001a2000000000000000000000000000000000000000000000000000000000000000002205109b9ea90f2a040880a305320410c0843d").to_vec(); let consensus_state = hex!("0a2a2f6962632e6c69676874636c69656e74732e7061726c69612e76312e436f6e73656e7375735374617465126c0a2056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b42110de82d5a8061a209c59cf0b5717cb6e2bd8620b7f3481605c8abcd45636bdf45c86db06338f0c5e22207a1dede35f5c835fecdc768324928cd0d9d9161e8529e1ba1e60451f3a9d088a").to_vec(); - let client = ParliaLightClient::default(); + let client = ParliaLightClient; let mock_consensus_state = BTreeMap::new(); let ctx = MockClientReader { client_state: None, @@ -673,7 +673,7 @@ mod test { ) { let any: Any = header.try_into().unwrap(); let header = Header::try_from(any.clone()).unwrap(); - let client = ParliaLightClient::default(); + let client = ParliaLightClient; let client_id = ClientId::new(&client.client_type(), 1).unwrap(); let mut mock_consensus_state = BTreeMap::new(); let trusted_cs = ConsensusState { @@ -743,7 +743,7 @@ mod test { #[rstest] #[case::localnet(localnet())] fn test_success_update_state_continuous(#[case] hp: Box) { - let client = ParliaLightClient::default(); + let client = ParliaLightClient; let client_id = ClientId::new(&client.client_type(), 1).unwrap(); let header_groups = hp.success_update_client_continuous_input(); @@ -800,7 +800,7 @@ mod test { let header = input.header; let any: Any = header.try_into().unwrap(); - let client = ParliaLightClient::default(); + let client = ParliaLightClient; let client_id = ClientId::new(&client.client_type(), 1).unwrap(); let mut mock_consensus_state = BTreeMap::new(); @@ -877,7 +877,7 @@ mod test { let header = Header::try_from(input.clone()).unwrap(); let trusted_height = header.trusted_height(); - let client = ParliaLightClient::default(); + let client = ParliaLightClient; let client_id = ClientId::new(&client.client_type(), 1).unwrap(); let mut mock_consensus_state = BTreeMap::new(); mock_consensus_state.insert(trusted_height, ConsensusState::default()); @@ -977,7 +977,7 @@ mod test { latest_height: Height, frozen: bool, ) -> Result { - let client = ParliaLightClient::default(); + let client = ParliaLightClient; let client_id = ClientId::new(client.client_type().as_str(), 0).unwrap(); let mut mock_consensus_state = BTreeMap::new(); mock_consensus_state.insert( @@ -1008,7 +1008,7 @@ mod test { #[test] fn test_success_submit_misbehavior() { - let client = ParliaLightClient::default(); + let client = ParliaLightClient; let client_id = ClientId::new(client.client_type().as_str(), 1).unwrap(); // Detect misbehavior @@ -1071,7 +1071,7 @@ mod test { consensus_state: BTreeMap::new(), }; - let client = ParliaLightClient::default(); + let client = ParliaLightClient; let client_id = ClientId::new(client.client_type().as_str(), 1).unwrap(); // fail: exactly same block diff --git a/light-client/src/errors.rs b/light-client/src/errors.rs index 45bb391..fc843c2 100644 --- a/light-client/src/errors.rs +++ b/light-client/src/errors.rs @@ -84,11 +84,10 @@ pub enum Error { MissingTurnLengthInEpochBlock(BlockNumber), MissingEpochInfoInEpochBlock(BlockNumber), MissingNextValidatorSet(BlockNumber), - MissingCurrentValidatorSet(BlockNumber), UnexpectedPreviousValidatorsHash(Height, Height, Hash, Hash), UnexpectedCurrentValidatorsHash(Height, Height, Hash, Hash), InvalidVerifyingHeaderLength(BlockNumber, usize), - InsufficientTrustedValidatorsInUntrustedValidators(Hash, usize, usize), + InsufficientHonestValidator(Hash, usize, usize), MissingValidatorToVerifySeal(BlockNumber), MissingValidatorToVerifyVote(BlockNumber), UnexpectedNextCheckpointHeader(BlockNumber, BlockNumber), @@ -98,6 +97,7 @@ pub enum Error { UnexpectedDifficultyNoTurn(BlockNumber, u64, usize), UnexpectedUntrustedValidatorsHashInEpoch(Height, Height, Hash, Hash), UnexpectedCurrentValidatorsHashInEpoch(Height, Height, Hash, Hash), + UnexpectedUntrustedValidators(BlockNumber, BlockNumber), // Vote attestation UnexpectedTooManyHeadersToFinalize(BlockNumber, usize), @@ -316,19 +316,12 @@ impl core::fmt::Display for Error { Error::UnexpectedVoteRelation(e1, e2, e3) => { write!(f, "UnexpectedVoteRelation : {} {} {:?}", e1, e2, e3) } - Error::InsufficientTrustedValidatorsInUntrustedValidators(e1, e2, e3) => { - write!( - f, - "InsufficientTrustedValidatorsInUntrustedValidators : {:?} {} {}", - e1, e2, e3 - ) + Error::InsufficientHonestValidator(e1, e2, e3) => { + write!(f, "InsufficientHonestValidator : {:?} {} {}", e1, e2, e3) } Error::MissingNextValidatorSet(e1) => { write!(f, "MissingNextValidatorSet : {}", e1) } - Error::MissingCurrentValidatorSet(e1) => { - write!(f, "MissingCurrentValidatorSet : {}", e1) - } Error::MissingValidatorToVerifySeal(e1) => { write!(f, "MissingValidatorToVerifySeal : {:?}", e1) } @@ -379,6 +372,9 @@ impl core::fmt::Display for Error { e1, e2, e3, e4 ) } + Error::UnexpectedUntrustedValidators(e1, e2) => { + write!(f, "UnexpectedUntrustedValidators : {} {}", e1, e2) + } Error::UnsupportedMinimumTimestamp(e1) => { write!(f, "UnsupportedMinimumTimestamp : {:?}", e1) } diff --git a/light-client/src/header/epoch.rs b/light-client/src/header/epoch.rs index d6a4a01..f2333bc 100644 --- a/light-client/src/header/epoch.rs +++ b/light-client/src/header/epoch.rs @@ -57,53 +57,56 @@ impl<'a> TrustedEpoch<'a> { pub fn new(inner: &'a Epoch) -> Self { Self { inner } } -} -#[derive(Clone, Debug, PartialEq)] -pub struct UntrustedEpoch<'a> { - inner: &'a Epoch, -} - -impl<'a> UntrustedEpoch<'a> { - pub fn new(inner: &'a Epoch) -> Self { - Self { inner } - } - pub fn checkpoint(&self) -> u64 { - self.inner.checkpoint() - } - pub fn try_borrow(&'a self, trusted_epoch: &TrustedEpoch) -> Result<&'a Epoch, Error> { - let (result, found, required) = self.contains(trusted_epoch); + pub fn verify_untrusted_voters(&self, untrusted_voter: &Validators) -> Result<(), Error> { + let (result, found, required) = + self.contains_at_least_one_honest_validator(untrusted_voter); if result { - return Ok(self.inner); + return Ok(()); } - Err(Error::InsufficientTrustedValidatorsInUntrustedValidators( + Err(Error::InsufficientHonestValidator( self.inner.hash, found, required, )) } - fn contains(&self, trusted_epoch: &TrustedEpoch) -> (bool, usize, usize) { - let trusted_validators = trusted_epoch.validators(); + pub fn contains_at_least_one_honest_validator( + &self, + untrusted_voters: &Validators, + ) -> (bool, usize, usize) { let mut trusted_validator_count = 0; - for x1 in self.inner.validators() { - if trusted_validators.contains(x1) { + for x1 in untrusted_voters { + if self.validators().contains(x1) { trusted_validator_count += 1; } } - let required = Self::threshold(trusted_validators.len()); + let required = Self::threshold(self.validators().len()); ( trusted_validator_count >= required, trusted_validator_count, required, ) } - fn threshold(validators_len: usize) -> usize { validators_len - ceil_div(validators_len * 2, 3) + 1 } } +#[derive(Clone, Debug, PartialEq)] +pub struct UntrustedEpoch<'a> { + inner: &'a Epoch, +} + +impl<'a> UntrustedEpoch<'a> { + pub fn new(inner: &'a Epoch) -> Self { + Self { inner } + } + pub fn checkpoint(&self) -> u64 { + self.inner.checkpoint() + } +} + #[derive(Clone, Debug, PartialEq)] pub enum EitherEpoch<'a> { Trusted(TrustedEpoch<'a>), @@ -117,16 +120,23 @@ impl<'a> EitherEpoch<'a> { EitherEpoch::Untrusted(v) => v.checkpoint(), } } + + pub fn epoch(&self) -> &'a Epoch { + match self { + EitherEpoch::Trusted(v) => v.inner, + EitherEpoch::Untrusted(v) => v.inner, + } + } } #[cfg(test)] mod test { use crate::errors::Error; - use crate::header::epoch::{Epoch, TrustedEpoch, UntrustedEpoch, ValidatorSet}; + use crate::header::epoch::{Epoch, TrustedEpoch, ValidatorSet}; #[test] - pub fn test_untrusted_epoch_try_borrow() { - let mut _assert_trusted = |x, y, c_val_borrowable| { + pub fn test_verify_voter() { + let mut _assert_trusted = |x, y, success: bool| { let trusted_validators: ValidatorSet = vec![ vec![1], vec![2], @@ -139,34 +149,18 @@ mod test { .into(); let trusted_epoch = Epoch::new(trusted_validators, 1); let trusted_epoch = TrustedEpoch::new(&trusted_epoch); - let untrusted_epoch = Epoch::new( - ValidatorSet { - validators: x, - hash: [0; 32], - }, - 1, - ); - let untrusted_epoch = UntrustedEpoch::new(&untrusted_epoch); - let (result, count, required) = untrusted_epoch.contains(&trusted_epoch); - assert_eq!(result, c_val_borrowable); + let (result, count, required) = + trusted_epoch.contains_at_least_one_honest_validator(&x); + assert_eq!(result, success); assert_eq!(count, y); assert_eq!(required, 3); - match untrusted_epoch.try_borrow(&trusted_epoch) { - Ok(borrowed) => { - if c_val_borrowable { - assert_eq!(borrowed, untrusted_epoch.inner); - } else { - unreachable!("unexpected borrowed") - } - } + match trusted_epoch.verify_untrusted_voters(&x) { + Ok(_) => assert!(success), Err(e) => { - if c_val_borrowable { - unreachable!("unexpected error {:?}", e); - } else { - match e { - Error::InsufficientTrustedValidatorsInUntrustedValidators(_, _, _) => {} - e => unreachable!("unexpected error type {:?}", e), - } + assert!(!success); + match e { + Error::InsufficientHonestValidator(_, _, _) => {} + e => unreachable!("unexpected error type {:?}", e), } } } @@ -302,15 +296,15 @@ mod test { #[test] pub fn test_trust_threshold() { - assert_eq!(1, UntrustedEpoch::threshold(1)); - assert_eq!(1, UntrustedEpoch::threshold(2)); - assert_eq!(2, UntrustedEpoch::threshold(3)); - assert_eq!(2, UntrustedEpoch::threshold(4)); - assert_eq!(2, UntrustedEpoch::threshold(5)); - assert_eq!(3, UntrustedEpoch::threshold(6)); - assert_eq!(3, UntrustedEpoch::threshold(7)); - assert_eq!(3, UntrustedEpoch::threshold(8)); - assert_eq!(4, UntrustedEpoch::threshold(9)); - assert_eq!(8, UntrustedEpoch::threshold(21)); + assert_eq!(1, TrustedEpoch::threshold(1)); + assert_eq!(1, TrustedEpoch::threshold(2)); + assert_eq!(2, TrustedEpoch::threshold(3)); + assert_eq!(2, TrustedEpoch::threshold(4)); + assert_eq!(2, TrustedEpoch::threshold(5)); + assert_eq!(3, TrustedEpoch::threshold(6)); + assert_eq!(3, TrustedEpoch::threshold(7)); + assert_eq!(3, TrustedEpoch::threshold(8)); + assert_eq!(4, TrustedEpoch::threshold(9)); + assert_eq!(8, TrustedEpoch::threshold(21)); } } diff --git a/light-client/src/header/eth_headers.rs b/light-client/src/header/eth_headers.rs index 472b47e..18b7eae 100644 --- a/light-client/src/header/eth_headers.rs +++ b/light-client/src/header/eth_headers.rs @@ -1,13 +1,12 @@ use alloc::vec::Vec; - use parlia_ibc_proto::ibc::lightclients::parlia::v1::EthHeader; use crate::errors::Error; use crate::errors::Error::MissingEpochInfoInEpochBlock; use crate::header::epoch::EitherEpoch::{Trusted, Untrusted}; -use crate::header::epoch::{EitherEpoch, Epoch, TrustedEpoch, UntrustedEpoch}; +use crate::header::epoch::{EitherEpoch, Epoch, TrustedEpoch}; -use crate::misc::{BlockNumber, ChainId}; +use crate::misc::{BlockNumber, ChainId, Validators}; use super::eth_header::ETHHeader; use super::BLOCKS_PER_EPOCH; @@ -29,13 +28,7 @@ impl ETHHeaders { let epoch = self.target.number / BLOCKS_PER_EPOCH; let checkpoint = epoch * BLOCKS_PER_EPOCH + previous_epoch.checkpoint(); let next_checkpoint = (epoch + 1) * BLOCKS_PER_EPOCH + current_epoch.checkpoint(); - let (c_val, n_val) = self.verify_header_size( - epoch, - checkpoint, - next_checkpoint, - previous_epoch, - current_epoch, - )?; + let n_val = self.verify_header_size(epoch, checkpoint, next_checkpoint, current_epoch)?; // Ensure all the headers are successfully chained. self.verify_cascading_fields()?; @@ -46,7 +39,7 @@ impl ETHHeaders { if h.number >= next_checkpoint { h.verify_seal(unwrap_n_val(h.number, &n_val)?, chain_id)?; } else if h.number >= checkpoint { - h.verify_seal(unwrap_c_val(h.number, &c_val)?, chain_id)?; + h.verify_seal(current_epoch.epoch(), chain_id)?; } else { h.verify_seal(previous_epoch.epoch(), chain_id)?; } @@ -57,16 +50,28 @@ impl ETHHeaders { // Ensure BLS signature is collect // At the just checkpoint BLS signature uses previous validator set. + let mut last_voters: Validators = Vec::new(); for h in &[child, grand_child] { let vote = h.get_vote_attestation()?; - if h.number > next_checkpoint { - vote.verify(h.number, unwrap_n_val(h.number, &n_val)?.validators())?; + last_voters = if h.number > next_checkpoint { + vote.verify(h.number, unwrap_n_val(h.number, &n_val)?.validators())? } else if h.number > checkpoint { - vote.verify(h.number, unwrap_c_val(h.number, &c_val)?.validators())?; + vote.verify(h.number, current_epoch.epoch().validators())? } else { - vote.verify(h.number, p_val)?; - } + vote.verify(h.number, p_val)? + }; } + + // Ensure voters for grand child are valid + verify_voters( + &last_voters, + grand_child, + next_checkpoint, + checkpoint, + current_epoch, + previous_epoch, + )?; + Ok(()) } @@ -112,18 +117,17 @@ impl ETHHeaders { )) } - fn verify_header_size<'a, 'b>( - &'b self, + fn verify_header_size( + &self, epoch: u64, checkpoint: u64, next_checkpoint: u64, - previous_epoch: &TrustedEpoch, - current_epoch: &'a EitherEpoch, - ) -> Result<(Option<&'a Epoch>, Option<&'b Epoch>), Error> { + current_epoch: &EitherEpoch, + ) -> Result, Error> { let hs: Vec<ÐHeader> = self.all.iter().filter(|h| h.number >= checkpoint).collect(); match current_epoch { - // ex) t=200 then 200 <= h < 411 (c_val(200) can be borrowed by p_val) - Untrusted(untrusted) => { + // ex) t=200 then 200 <= h < 411 (at least 1 honest c_val(200)' can be in p_val) + Untrusted(_) => { // Ensure headers are before the next_checkpoint if hs.iter().any(|h| h.number >= next_checkpoint) { return Err(Error::UnexpectedNextCheckpointHeader( @@ -131,35 +135,27 @@ impl ETHHeaders { next_checkpoint, )); } - - // Ensure c_val is validated by trusted p_val when the checkpoint header is found - if hs.is_empty() { - Ok((None, None)) - } else { - Ok((Some(untrusted.try_borrow(previous_epoch)?), None)) - } + Ok(None) } - // ex) t=201 then 201 <= h < 611 (n_val(400) can be borrowed by c_val(200)) - Trusted(trusted) => { + // ex) t=201 then 201 <= h < 611 (at least 1 honest n_val(400) can be in c_val(200)) + Trusted(_) => { // Get next_epoch if epoch after checkpoint ex) 400 let next_epoch = match hs.iter().find(|h| h.is_epoch()) { Some(h) => h .epoch .as_ref() .ok_or_else(|| MissingEpochInfoInEpochBlock(h.number))?, - None => return Ok((Some(trusted.epoch()), None)), + None => return Ok(None), }; // Finish if no headers over next checkpoint were found let hs: Vec<&ÐHeader> = hs.iter().filter(|h| h.number >= next_checkpoint).collect(); if hs.is_empty() { - return Ok((Some(trusted.epoch()), None)); + return Ok(None); } - // Ensure n_val(400) can be borrowed by c_val(200) let next_next_checkpoint = (epoch + 2) * BLOCKS_PER_EPOCH + next_epoch.checkpoint(); - UntrustedEpoch::new(next_epoch).try_borrow(trusted)?; // Ensure headers are before the next_next_checkpoint if hs.iter().any(|h| h.number >= next_next_checkpoint) { @@ -168,7 +164,7 @@ impl ETHHeaders { next_next_checkpoint, )); } - Ok((Some(trusted.epoch()), Some(next_epoch))) + Ok(Some(next_epoch)) } } } @@ -222,8 +218,30 @@ fn unwrap_n_val<'a>(n: BlockNumber, n_val: &'a Option<&'a Epoch>) -> Result<&'a n_val.ok_or_else(|| Error::MissingNextValidatorSet(n)) } -fn unwrap_c_val<'a>(n: BlockNumber, c_val: &'a Option<&'a Epoch>) -> Result<&'a Epoch, Error> { - c_val.ok_or_else(|| Error::MissingCurrentValidatorSet(n)) +fn verify_voters( + voters: &Validators, + h: ÐHeader, + next_checkpoint: BlockNumber, + checkpoint: BlockNumber, + current_epoch: &EitherEpoch, + previous_epoch: &TrustedEpoch, +) -> Result<(), Error> { + if h.number > next_checkpoint { + match current_epoch { + Trusted(e) => e.verify_untrusted_voters(voters)?, + _ => { + return Err(Error::UnexpectedUntrustedValidators( + h.number, + next_checkpoint, + )) + } + } + } else if h.number > checkpoint { + if let Untrusted(_) = current_epoch { + previous_epoch.verify_untrusted_voters(voters)?; + } + } + Ok(()) } #[cfg(test)] @@ -232,12 +250,12 @@ mod test { use crate::header::constant::BLOCKS_PER_EPOCH; use crate::header::eth_header::{get_validator_bytes_and_tern_term, ETHHeader}; - use crate::header::eth_headers::ETHHeaders; + use crate::header::eth_headers::{verify_voters, ETHHeaders}; use crate::fixture::*; use crate::header::epoch::{EitherEpoch, Epoch, TrustedEpoch, UntrustedEpoch}; use crate::header::Header; - use crate::misc::{ChainId, Validators}; + use crate::misc::Validators; use hex_literal::hex; use light_client::types::Any; use rstest::rstest; @@ -412,6 +430,95 @@ mod test { header.headers.verify_finalized().unwrap(); } + #[test] + fn test_success_verify_voters() { + let mut h = localnet().previous_epoch_header(); + let p_vals = vec![vec![1], vec![2]]; + let p_epoch = Epoch::new(p_vals.into(), 1); + let pt_epoch = TrustedEpoch::new(&p_epoch); + let c_vals = vec![vec![1], vec![2]]; + let c_epoch = Epoch::new(c_vals.into(), 1); + + // after next checkpoint + h.number = 412; + verify_voters( + &vec![vec![1]], + &h, + 411, + 211, + &EitherEpoch::Trusted(TrustedEpoch::new(&c_epoch)), + &pt_epoch, + ) + .unwrap(); + + // after checkpoint + h.number = 212; + verify_voters( + &vec![vec![1]], + &h, + 411, + 211, + &EitherEpoch::Untrusted(UntrustedEpoch::new(&c_epoch)), + &pt_epoch, + ) + .unwrap(); + + // other + h.number = 211; + verify_voters( + &vec![vec![1]], + &h, + 411, + 211, + &EitherEpoch::Untrusted(UntrustedEpoch::new(&c_epoch)), + &pt_epoch, + ) + .unwrap(); + } + + #[test] + fn test_error_verify_voters() { + let mut h = localnet().previous_epoch_header(); + let p_vals = vec![vec![1], vec![2]]; + let p_epoch = Epoch::new(p_vals.into(), 1); + let pt_epoch = TrustedEpoch::new(&p_epoch); + let c_vals = vec![vec![1], vec![2]]; + let c_epoch = Epoch::new(c_vals.into(), 1); + + // after next checkpoint + h.number = 412; + verify_voters( + &vec![vec![1]], + &h, + 411, + 211, + &EitherEpoch::Untrusted(UntrustedEpoch::new(&c_epoch)), + &pt_epoch, + ) + .unwrap_err(); + verify_voters( + &vec![vec![0]], + &h, + 411, + 211, + &EitherEpoch::Trusted(TrustedEpoch::new(&c_epoch)), + &pt_epoch, + ) + .unwrap_err(); + + // after checkpoint + h.number = 212; + verify_voters( + &vec![vec![0]], + &h, + 411, + 211, + &EitherEpoch::Untrusted(UntrustedEpoch::new(&c_epoch)), + &pt_epoch, + ) + .unwrap_err(); + } + #[test] fn test_error_verify_finalized_no_finalized_header() { let header= hex!("0a222f6962632e6c69676874636c69656e74732e7061726c69612e76312e48656164657212ed200ae9080ae608f90463a0794978ac680964fb5ada43366fa4d33a490c93ec6893304ddee68a59f2cafabaa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794d9a13701eafb76870cb220843b8c6476824bfa15a0558eacf75665a00d1eef186ffc4f79985db5e5fcb1aa24892df5d600ae869313a09f0bb93d54df1fcbfd84d4173496de9cff0f403319bbbaf15791ccde774b73d8a03cd1ebc99cd975182c58de47be968c97658cff4c465e20654185f408a851403cb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000028211308402625a008229a884669fb2e3b90223d98301040c846765746889676f312e32312e3132856c696e75780000d24ec9e8048fdaaa7e6631e438625ca25c857a3727ea28e565b876532dd999985816f1df35a5d6359177f1d49bfb3c20e25d6760197246ad0b6b8efb77ad316a0f31360c3733cabd6ca7876ea32e7a748c697d01345145485561305b24b6c305acd27ad7aff76367fd3a1dfe8da19afba969c8464f37a29e60923c3a85cfacbdef18daa782d5724f13d415f98cb2e42bc54d19116d2348ac83461e2e0915d508ad976963272de9af796035a7c68771d03c92709aa174ce1e8723cb6d7d1f6d960790e83c59e1f9867721e6302520a30a44e04db2de85453e0936b441c339a26d10cfa71b50b359d8b4d1e5fd24f5a99712ed2e5a8f7180621828a1ae567b86ff60792ff27f2fd62d410aa8b9b858316495867f833309f8ae0fb860834538868d2c79371ead2f10fc7229fd3b3aaf1d7d8607fd7e1d2efba7df1008c105fdab4ad8029f86cfde1e3aa7dd24194fcf07502bb6c281fc3cbc08ad8cb467de57cbfd1bb93fabe72f77ece8f991a2b7a4d7fb301d54547c6cb4b612d06ff84882112ea042dfe9761fb9a677b088a868f237a171d511bea581f643844a1c98267902391882112fa0794978ac680964fb5ada43366fa4d33a490c93ec6893304ddee68a59f2cafaba80321f35c8454a2691a2efff6894cae6277ea06390978294fe0d10fe03f432fd07440d802ca0858a2d6138230b37d1a9148a3d76fbe8492854e637c9aad7494e3401a0000000000000000000000000000000000000000000000000000000000000000088000000000000000080a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b4218080a000000000000000000000000000000000000000000000000000000000000000000ad5060ad206f9034fa09696424e13500cdc742b049c6459c0bc4cb357eab5d9fb48a2e79787c8897a1fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794d9a13701eafb76870cb220843b8c6476824bfa15a0558eacf75665a00d1eef186ffc4f79985db5e5fcb1aa24892df5d600ae869313a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000028211318402625a008084669fb2e6b90111d98301040c846765746889676f312e32312e3132856c696e75780000d24ec9e8f8ae0fb860b6de9ffe941751cd11463bbd4bde69d3bc2b79868f669f9e2c4c327f036e6d558b013469a91670607dd88d63dc50b95d00719e118a17e5b72418b6ae4bef7aebbcdaa44b79f8c0677ac32c71312d84db44e6e9216bd84fb97c7d0701a4a03431f84882112fa0794978ac680964fb5ada43366fa4d33a490c93ec6893304ddee68a59f2cafaba821130a09696424e13500cdc742b049c6459c0bc4cb357eab5d9fb48a2e79787c8897a1f80e5619874ecb4463f8d86981d939ddb4d0eaa151c6080c7067788e06699789d0a415e921b8824e8adc7907e5bbccec9373b22c7fe8a48527e9348aa957871b63300a0000000000000000000000000000000000000000000000000000000000000000088000000000000000080a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b4218080a000000000000000000000000000000000000000000000000000000000000000000ad5060ad206f9034fa0e3aa9bc64f82ccd7e70ec415d73263d9da9f3bb44b78bed500033379df9be8aaa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794d9a13701eafb76870cb220843b8c6476824bfa15a0558eacf75665a00d1eef186ffc4f79985db5e5fcb1aa24892df5d600ae869313a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000028211328402625a008084669fb2e9b90111d98301040c846765746889676f312e32312e3132856c696e75780000d24ec9e8f8ae0fb86095a056c77bdeb59da26e665d69e75cc5667cd43df693a55f20f5f248483a3ce10cdd996b29c7fd6ba3f9e31cb4ac089c0b5bc13e77d83f17096d88f280b8d47ffd8b58b66bd760d0ccd4d26e0c2a7bb3ec168a06a815cfbdde9a2e18c32ac74ef848821130a09696424e13500cdc742b049c6459c0bc4cb357eab5d9fb48a2e79787c8897a1f821131a0e3aa9bc64f82ccd7e70ec415d73263d9da9f3bb44b78bed500033379df9be8aa80c04c6f70fa142c7486f7bed7e29a247f1203d58c7481c9aefef1193e6404112e32486f8d4dc8c7c558ae0b1bf860f6e05d425b7726ba7a4e2dac6e3e3eb83d3d00a0000000000000000000000000000000000000000000000000000000000000000088000000000000000080a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b4218080a00000000000000000000000000000000000000000000000000000000000000000120310e8201a9506f90312f901f1a094e7dcd05ddcc3923085b451330f3aa5ce5a628d6685506d99cb09b3aef0e11ea065b83aa9b59125f9b090432f556c6ff947b5708eb11ca5ea26342392860be00aa0b116ef7733a93eed23f018027c116e60436a228a9f9173bb9b0c40eb71216da6a064c0a17f12a753c3fc032723866ac267ad8b7e05e7aa2e75bb680175d936617580a01a41c640130c53b3c90b1b5c691ed467218cee97aade5aac9306e72865851e27a004371241b9d6f35e1f361f2109d19a9192a9c0c749b2bd15fcb131a1a6ce5e3ba00577e3e2c4649c5a23cbdabe0bbfed7cdf6e85c136d84d58127cdec86264ad6ea070c0f30031f40c8017dbfc2ef008e6a3aae2e3105a654e0c5439b6104752882ea08f81903ec8515875682785142e1f92bdeaf65fccd5d0cf78b1ff2905a07e5883a03052420ba2d24a04d3f830584d3dbd6907b6d82bab84ddd806d03470e2c9d51ca06fb1a1498c2c8f93944a4f672ff4e982480ad181c835c0d8078159c517c7977aa013a426820f7b7249edc97cc5c002e653ac84b437f3ac12ac940c3d4b09e09827a09fbc54eac488b27315b09a1afa8d12f168e4c4cb5aea2d9a6ab5e7266da2f7e8a077c5e5cd5bd518bc509ee5e71790f1e42e492e23875b097e565cff8e809e7c8aa0a1575ef06513a19d2a28390e83958d2a3ffe166b530255b0fb5559d33409914d80f8b18080a0dc77b6ae50b675036e77b31973c79ec60c28c0d2c57b03ad99c2acfff2f0cd4e80a063a8a6161448a60a47ddbafa00899bed224e9f80072b35a1dbc64a82e85cd9b5a05e0f116451aaa1baab3f3abff2793c8318050eeed6bf62d464d343a11d86eb2880808080808080a0abbb1987d09a71106f586030d1ab913bae0008e2a7dec0d08f2d60cd30fb2ac8a096c706907bfc6472dd88315cb8e21ee6f60a661cd8050065e2ba387023ee96858080f869a020b1e2b1f9852058ee0aaadca3c963f77f6483a1a51c644d79386bcada360583b846f8440180a0e39304f0ec064a98e4b0a96432dfb0a9e4c7fd0f26a6bbcf9c75bff68c51a7a9a0b3d632130dcb5cb583b47ec0623e59ca3703e6e2564f144272b597f3e3511ba822448fdaaa7e6631e438625ca25c857a3727ea28e565b876532dd999985816f1df35a5d6359177f1d49bfb3c20e25d6760197246ad0b6b8efb77ad316a0f31360c3733cabd6c2244a7876ea32e7a748c697d01345145485561305b24b6c305acd27ad7aff76367fd3a1dfe8da19afba969c8464f37a29e60923c3a85cfacbdef18daa782d5724f13d415f98c2244b2e42bc54d19116d2348ac83461e2e0915d508ad976963272de9af796035a7c68771d03c92709aa174ce1e8723cb6d7d1f6d960790e83c59e1f9867721e6302520a30a442244e04db2de85453e0936b441c339a26d10cfa71b50b359d8b4d1e5fd24f5a99712ed2e5a8f7180621828a1ae567b86ff60792ff27f2fd62d410aa8b9b858316495867f83332a448fdaaa7e6631e438625ca25c857a3727ea28e565b876532dd999985816f1df35a5d6359177f1d49bfb3c20e25d6760197246ad0b6b8efb77ad316a0f31360c3733cabd6c2a44b2e42bc54d19116d2348ac83461e2e0915d508ad976963272de9af796035a7c68771d03c92709aa174ce1e8723cb6d7d1f6d960790e83c59e1f9867721e6302520a30a442a44d9a13701eafb76870cb220843b8c6476824bfa15b9ebdc1d1a70721d7f9c57622e0a5d1175df1e09672ab1e8909bf9a9433592107024bd8a3ad47fbbdca199ede96c50d22a44e04db2de85453e0936b441c339a26d10cfa71b50b359d8b4d1e5fd24f5a99712ed2e5a8f7180621828a1ae567b86ff60792ff27f2fd62d410aa8b9b858316495867f833330093808").to_vec(); diff --git a/light-client/src/header/mod.rs b/light-client/src/header/mod.rs index 90b826b..9c2a98a 100644 --- a/light-client/src/header/mod.rs +++ b/light-client/src/header/mod.rs @@ -460,7 +460,7 @@ pub(crate) mod test { let trusted_height = new_height(0, 201); let current_epoch = &hp.epoch_header().epoch.unwrap(); let previous_epoch = &Epoch::new(to_validator_set([1u8; 32]), 1); - let (c_val, p_val) = verify_epoch( + let (c_val, _) = verify_epoch( &cs, &hp.epoch_header(), height, @@ -470,9 +470,7 @@ pub(crate) mod test { ) .unwrap(); match c_val { - EitherEpoch::Untrusted(r) => { - assert!(r.try_borrow(&p_val).is_err()) - } + EitherEpoch::Untrusted(_r) => {} _ => unreachable!("unexpected trusted"), } diff --git a/light-client/src/header/vote_attestation.rs b/light-client/src/header/vote_attestation.rs index 470bd87..50e48a0 100644 --- a/light-client/src/header/vote_attestation.rs +++ b/light-client/src/header/vote_attestation.rs @@ -45,7 +45,11 @@ pub struct VoteAttestation { } impl VoteAttestation { - pub fn verify(&self, number: BlockNumber, validators: &Validators) -> Result<(), Error> { + pub fn verify( + &self, + number: BlockNumber, + validators: &Validators, + ) -> Result { if self.vote_address_set.count() > validators.len() { return Err(Error::UnexpectedVoteAddressCount( number, @@ -53,6 +57,7 @@ impl VoteAttestation { validators.len(), )); } + let mut voted = Vec::new(); let mut voted_addr = Vec::new(); for (i, val) in validators.iter().enumerate() { if !self.vote_address_set.get(i) { @@ -62,6 +67,7 @@ impl VoteAttestation { let bls_pub_key = PublicKey::from_bytes(bls_pub_key_bytes) .map_err(|e| Error::UnexpectedBLSPubkey(number, e))?; voted_addr.push(bls_pub_key); + voted.push(val.clone()) } let required = ceil_div(validators.len() * 2, 3); @@ -82,7 +88,7 @@ impl VoteAttestation { pub_keys_ref.len(), )); } - Ok(()) + Ok(voted) } }