Skip to content

Commit

Permalink
Enable verification with the new validator set trusted by the already…
Browse files Browse the repository at this point in the history
… trusted validator set (#37)

Support fallback trusted validators

Signed-off-by: Naohiro Yoshida <naohiro.yoshida@datachain.jp>
  • Loading branch information
yoshidan authored Dec 5, 2023
1 parent 9d3318a commit c78c881
Show file tree
Hide file tree
Showing 7 changed files with 482 additions and 23 deletions.
20 changes: 20 additions & 0 deletions light-client/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,15 @@ pub enum Error {
ProofRLPError(rlp::DecoderError),
InvalidProofFormatError(Vec<u8>),
MissingValidatorInEpochBlock(BlockNumber),
MissingNextValidatorSet(BlockNumber),
UnexpectedPreviousValidatorsHash(Height, Height, Hash, Hash),
UnexpectedCurrentValidatorsHash(Height, Height, Hash, Hash),
InvalidVerifyingHeaderLength(BlockNumber, usize),
ValidatorNotTrusted(Hash),
MissingValidatorToVerifySeal(BlockNumber),
MissingValidatorToVerifyVote(BlockNumber),
UnexpectedNextCheckpointHeader(BlockNumber, BlockNumber),
UnexpectedNextNextCheckpointHeader(BlockNumber, BlockNumber),

// Vote attestation
UnexpectedTooManyHeadersToFinalize(BlockNumber, usize),
Expand Down Expand Up @@ -280,6 +285,21 @@ impl core::fmt::Display for Error {
Error::ValidatorNotTrusted(e1) => {
write!(f, "ValidatorNotTrusted : {:?}", e1)
}
Error::MissingNextValidatorSet(e1) => {
write!(f, "MissingNextValidatorSet : {:?}", e1)
}
Error::MissingValidatorToVerifySeal(e1) => {
write!(f, "MissingValidatorToVerifySeal : {:?}", e1)
}
Error::MissingValidatorToVerifyVote(e1) => {
write!(f, "MissingValidatorToVerifyVote : {:?}", e1)
}
Error::UnexpectedNextCheckpointHeader(e1, e2) => {
write!(f, "UnexpectedNextCheckpointHeader : {} {}", e1, e2)
}
Error::UnexpectedNextNextCheckpointHeader(e1, e2) => {
write!(f, "UnexpectedNextNextCheckpointHeader : {} {}", e1, e2)
}
}
}
}
Expand Down
8 changes: 8 additions & 0 deletions light-client/src/header/eth_header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use rlp::{Rlp, RlpStream};
use parlia_ibc_proto::ibc::lightclients::parlia::v1::EthHeader as RawETHHeader;

use crate::errors::Error;
use crate::header::validator_set::ValidatorSet;

use crate::header::vote_attestation::VoteAttestation;
use crate::misc::{Address, BlockNumber, ChainId, Hash, RlpIterator, Validators};
Expand Down Expand Up @@ -264,6 +265,13 @@ impl ETHHeader {
)
}

pub fn get_validator_set(&self) -> Result<ValidatorSet, Error> {
Ok(self
.get_validator_bytes()
.ok_or_else(|| Error::MissingValidatorInEpochBlock(self.number))?
.into())
}

pub fn is_epoch(&self) -> bool {
self.number % BLOCKS_PER_EPOCH == 0
}
Expand Down
212 changes: 200 additions & 12 deletions light-client/src/header/eth_headers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ impl ETHHeaders {
current_validators: &ValidatorSet,
previous_validators: &ValidatorSet,
) -> Result<(), Error> {
// Ensure the header after the next or next checkpoint must not exist.
let epoch = self.target.number / BLOCKS_PER_EPOCH;
let next_checkpoint = (epoch + 1) * BLOCKS_PER_EPOCH + current_validators.checkpoint();
let maybe_next_validators =
self.verify_header_size(epoch, next_checkpoint, current_validators)?;

// Ensure all the headers are successfully chained.
for (i, header) in self.all.iter().enumerate() {
if i < self.all.len() - 1 {
Expand All @@ -31,13 +37,15 @@ impl ETHHeaders {
}
}

let previous_validators = previous_validators.validators()?;

// Ensure valid seals
let epoch = self.target.number / BLOCKS_PER_EPOCH;
let checkpoint = epoch * BLOCKS_PER_EPOCH + checkpoint(previous_validators);
let checkpoint = epoch * BLOCKS_PER_EPOCH + previous_validators.checkpoint();
let previous_validators = previous_validators.validators()?;
for h in self.all.iter() {
if h.number >= checkpoint {
if h.number >= next_checkpoint {
let next_validators =
get_next_validators(h.number, maybe_next_validators.as_ref())?;
h.verify_seal(next_validators, chain_id)?;
} else if h.number >= checkpoint {
h.verify_seal(current_validators.validators()?, chain_id)?;
} else {
h.verify_seal(previous_validators, chain_id)?;
Expand All @@ -51,7 +59,11 @@ impl ETHHeaders {
// At the just checkpoint BLS signature uses previous validator set.
for h in headers_for_finalize {
let vote = h.get_vote_attestation()?;
if h.number > checkpoint {
if h.number > next_checkpoint {
let next_validators =
get_next_validators(h.number, maybe_next_validators.as_ref())?;
vote.verify(h.number, next_validators)?;
} else if h.number > checkpoint {
vote.verify(h.number, current_validators.validators()?)?;
} else {
vote.verify(h.number, previous_validators)?;
Expand Down Expand Up @@ -92,6 +104,47 @@ impl ETHHeaders {
last_error.map(alloc::boxed::Box::new),
))
}

fn verify_header_size(
&self,
epoch: u64,
next_checkpoint: u64,
current_validators: &ValidatorSet,
) -> Result<Option<ValidatorSet>, Error> {
let mut maybe_next_validators: Option<ValidatorSet> = None;

for h in self.all.iter() {
// t=200 then 200 <= h < 411 (c_val(200) can be trusted by p_val)
if self.target.is_epoch() {
if h.number >= next_checkpoint {
return Err(Error::UnexpectedNextCheckpointHeader(
self.target.number,
h.number,
));
}
} else {
// t=201 then 201 <= h < 611 (n_val(400) can be trusted by c_val(200))
if h.is_epoch() {
let mut next_validators: ValidatorSet = h.get_validator_set()?;
next_validators.trust(current_validators.validators()?);
maybe_next_validators = Some(next_validators);
} else if h.number >= next_checkpoint {
let next_validators = maybe_next_validators
.as_mut()
.ok_or_else(|| Error::MissingNextValidatorSet(h.number))?;
let next_next_checkpoint =
(epoch + 2) * BLOCKS_PER_EPOCH + next_validators.checkpoint();
if h.number >= next_next_checkpoint {
return Err(Error::UnexpectedNextNextCheckpointHeader(
self.target.number,
h.number,
));
}
}
}
}
Ok(maybe_next_validators)
}
}

impl TryFrom<Vec<EthHeader>> for ETHHeaders {
Expand Down Expand Up @@ -138,24 +191,24 @@ fn verify_finalized(
Ok(())
}

/// for example when the validator count is 21 the checkpoint is 211, 411, 611 ...
/// https://github.com/bnb-chain/bsc/blob/48aaee69e9cb50fc2cedf1398ae4b98b099697db/consensus/parlia/parlia.go#L607
/// https://github.com/bnb-chain/bsc/blob/48aaee69e9cb50fc2cedf1398ae4b98b099697db/consensus/parlia/snapshot.go#L191
fn checkpoint(validators: &Validators) -> u64 {
let validator_size = validators.len() as u64;
validator_size / 2 + 1
fn get_next_validators(number: u64, v: Option<&ValidatorSet>) -> Result<&Validators, Error> {
v.ok_or_else(|| Error::MissingNextValidatorSet(number))?
.validators()
}

#[cfg(test)]
mod test {
use crate::errors::Error;
use crate::header::constant::BLOCKS_PER_EPOCH;
use crate::header::eth_header::ETHHeader;
use crate::header::eth_headers::ETHHeaders;
use crate::header::testdata::*;
use crate::header::validator_set::ValidatorSet;
use crate::header::Header;
use crate::misc::Validators;
use hex_literal::hex;
use light_client::types::Any;
use std::vec;

fn trust(mut v: ValidatorSet) -> ValidatorSet {
v.trusted = true;
Expand Down Expand Up @@ -345,6 +398,141 @@ mod test {
}
}

#[test]
fn test_error_next_checkpoint_header_found_target_epoch() {
let f = |mut headers: ETHHeaders,
c_val: ValidatorSet,
p_val: ValidatorSet,
include_limit: bool| {
let epoch = headers.target.number / BLOCKS_PER_EPOCH;
let next_epoch_checkpoint = (epoch + 1) * BLOCKS_PER_EPOCH + c_val.checkpoint();
loop {
let last = headers.all.last().unwrap();
let drift = u64::from(!include_limit);
if last.number >= (next_epoch_checkpoint - drift) {
break;
}
let mut next = last.clone();
next.number = last.number + 1;
headers.all.push(next);
}
let result = headers.verify(&mainnet(), &c_val, &p_val).unwrap_err();
if include_limit {
match result {
Error::UnexpectedNextCheckpointHeader(e1, e2) => {
assert_eq!(e1, headers.target.number);
assert_eq!(e2, next_epoch_checkpoint);
}
err => unreachable!("err {:?}", err),
};
} else {
match result {
Error::UnexpectedHeaderRelation(_, _, _, _, _, _) => {}
err => unreachable!("err {:?}", err),
};
}
};
let v = vec![
header_31297200(),
header_31297201(), // checkpoint
header_31297202(),
];
let headers = ETHHeaders {
target: v[0].clone(),
all: v,
};
let c_val = trust(header_31297200().get_validator_bytes().unwrap().into());
let p_val = trust(validators_in_31297000().into());
f(headers.clone(), c_val, p_val.clone(), true);

let c_val = trust(header_31297200().get_validator_bytes().unwrap().into());
f(headers, c_val, p_val, false);
}

#[test]
fn test_error_next_next_checkpoint_header_found() {
let f = |mut headers: ETHHeaders,
c_val: ValidatorSet,
p_val: ValidatorSet,
n_val_header: ETHHeader,
include_limit: bool| {
let n_val: ValidatorSet = n_val_header.get_validator_bytes().unwrap().into();
let epoch = headers.target.number / BLOCKS_PER_EPOCH;
let next_next_epoch_checkpoint = (epoch + 2) * BLOCKS_PER_EPOCH + n_val.checkpoint();
loop {
let last = headers.all.last().unwrap();
let drift = u64::from(!include_limit);
if last.number >= next_next_epoch_checkpoint - drift {
break;
}
let mut next = last.clone();
next.number = last.number + 1;
if next.is_epoch() {
// set n_val
next.extra_data = n_val_header.extra_data.clone()
}
headers.all.push(next);
}
let result = headers.verify(&mainnet(), &c_val, &p_val).unwrap_err();
if include_limit {
match result {
Error::UnexpectedNextNextCheckpointHeader(e1, e2) => {
assert_eq!(e1, headers.target.number);
assert_eq!(e2, next_next_epoch_checkpoint);
}
err => unreachable!("err {:?}", err),
}
} else {
match result {
Error::UnexpectedHeaderRelation(_, _, _, _, _, _) => {}
err => unreachable!("err {:?}", err),
}
}
};
let headers = create_after_checkpoint_headers();
let c_val = trust(header_31297200().get_validator_bytes().unwrap().into());
let p_val = trust(empty());
let n_val_header = header_31297200();
f(
headers.clone(),
c_val.clone(),
p_val.clone(),
n_val_header.clone(),
true,
);
f(headers, c_val, p_val, n_val_header.clone(), false);

let headers = create_before_checkpoint_headers();
let p_val = ValidatorSet::from(validators_in_31297000());
let c_val = trust(ValidatorSet::from(
header_31297200().get_validator_bytes().unwrap(),
));
f(
headers.clone(),
c_val.clone(),
p_val.clone(),
n_val_header.clone(),
true,
);
f(
headers,
c_val.clone(),
p_val.clone(),
header_31297200(),
false,
);

let headers = create_across_checkpoint_headers();
f(
headers.clone(),
c_val.clone(),
p_val.clone(),
n_val_header.clone(),
true,
);
f(headers, c_val, p_val, n_val_header, false);
}

fn create_before_checkpoint_headers() -> ETHHeaders {
let v = vec![header_31297208(), header_31297209(), header_31297210()];
ETHHeaders {
Expand Down
28 changes: 27 additions & 1 deletion light-client/src/header/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ fn verify_validator_set(
consensus_state.current_validators_hash,
));
}

// Try set trust by previous trusted validators
current_validators.trust(previous_validators.validators()?)
} else {
if header_epoch != trusted_epoch {
return Err(Error::UnexpectedTrustedHeight(
Expand Down Expand Up @@ -406,12 +409,18 @@ pub(crate) mod test {
}

fn to_validator_set(h: Hash) -> ValidatorSet {
let validators: Validators = vec![];
let validators: Validators = vec![vec![1]];
let mut v: ValidatorSet = validators.into();
v.hash = h;
v
}

fn to_validator_set_with_validators(h: Hash, v: Validators) -> ValidatorSet {
let mut v: ValidatorSet = v.into();
v.hash = h;
v
}

#[test]
fn test_success_verify_validator_set() {
let cs = ConsensusState {
Expand All @@ -423,6 +432,8 @@ pub(crate) mod test {

let height = new_height(0, 400);
let trusted_height = new_height(0, 201);

// Same validator set as previous
let current_validators = &mut to_validator_set([3u8; 32]);
let previous_validators = &mut to_validator_set(cs.current_validators_hash);
verify_validator_set(
Expand All @@ -434,6 +445,21 @@ pub(crate) mod test {
current_validators,
)
.unwrap();
assert!(current_validators.trusted);

let current_validators = &mut to_validator_set([3u8; 32]);
let previous_validators =
&mut to_validator_set_with_validators(cs.current_validators_hash, vec![vec![2]]);
verify_validator_set(
&cs,
true,
height,
trusted_height,
previous_validators,
current_validators,
)
.unwrap();
assert!(!current_validators.trusted);

let height = new_height(0, 599);
let trusted_height = new_height(0, 400);
Expand Down
Loading

0 comments on commit c78c881

Please sign in to comment.