Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 4 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,11 @@ NOTE: This project is currently under heavy development. Features may change or
Environment variables can be used to change settings.
Each configuration must be determined at build time, not at run time.

### Blocks per epoch
You can change the blocks per epoch for localnet.
This is available in dev feature only.

```sh
BSC_BLOCKS_PER_EPOCH=20 cargo build --features=dev
```

### Build Parameters

Parameters can be specified to check for acceptable headers at build time.

| Name | Description |
| --- |-----------------------------------------------------------------------------------------------------------------------------|
| `MINIMUM_TIMESTAMP_SUPPORTED` | Timestamp of the lowest header this light client will accept. All the ForkSpec must be greater than or equal to this value. |
| `MINIMUM_HEIGHT_SUPPORTED` | Height of the lowest header this light client will accept. All the ForkSpec must be greater than or equal to this value. |
| Name | Description |
| --- |------------------------------------------------------------------------------------------------------------------------------|
| `MINIMUM_TIMESTAMP_SUPPORTED` | Timestamp(millisecond) of the lowest header this light client will accept. All the ForkSpec must be greater than or equal to this value. |
| `MINIMUM_HEIGHT_SUPPORTED` | Height of the lowest header this light client will accept. All the ForkSpec must be greater than or equal to this value. |
8 changes: 0 additions & 8 deletions light-client/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,6 @@ fn main() {
use std::io::Write;
let mut file = std::fs::File::create("src/header/constant.rs").unwrap();
let mut values: Vec<String> = vec![];
{
let blocks_per_epoch =
std::env::var("BSC_BLOCKS_PER_EPOCH").unwrap_or_else(|_| "500".to_string());
values.push(format!(
"pub const BLOCKS_PER_EPOCH: u64 = {};",
blocks_per_epoch
));
}
let minimum_time_stamp_supported =
std::env::var("MINIMUM_TIMESTAMP_SUPPORTED").unwrap_or_else(|_| "0".to_string());
values.push(format!(
Expand Down
112 changes: 98 additions & 14 deletions light-client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -373,13 +373,14 @@ impl InnerLightClient {
return Err(Error::ClientFrozen(client_id));
}

let mut misbehaviour = misbehaviour;
let trusted_consensus_state1 = ConsensusState::try_from(any_consensus_state1)?;
let trusted_consensus_state2 = ConsensusState::try_from(any_consensus_state2)?;
let new_client_state = client_state.check_misbehaviour_and_update_state(
ctx.host_timestamp(),
&trusted_consensus_state1,
&trusted_consensus_state2,
&misbehaviour,
&mut misbehaviour,
)?;

let prev_state = self.make_prev_states(
Expand Down Expand Up @@ -523,21 +524,14 @@ mod test {
use crate::client_state::ClientState;
use crate::consensus_state::ConsensusState;

use crate::fixture::{localnet, Network};
use crate::fixture::{fork_spec_after_lorentz, fork_spec_after_pascal, localnet, Network};
use crate::header::Header;

use crate::fork_spec::{ForkSpec, HeightOrTimestamp};
use crate::fork_spec::HeightOrTimestamp;
use crate::misbehaviour::Misbehaviour;
use crate::misc::{new_height, Address, ChainId, Hash};
use alloc::boxed::Box;

fn after_pascal() -> ForkSpec {
ForkSpec {
height_or_timestamp: HeightOrTimestamp::Height(0),
additional_header_item_count: 1, // requestsHash
}
}

impl Default for ClientState {
fn default() -> Self {
ClientState {
Expand All @@ -550,7 +544,7 @@ mod test {
max_clock_drift: core::time::Duration::new(1, 0),
latest_height: Default::default(),
frozen: false,
fork_specs: vec![after_pascal()],
fork_specs: vec![fork_spec_after_pascal(), fork_spec_after_lorentz()],
}
}
}
Expand Down Expand Up @@ -808,6 +802,90 @@ mod test {
};
}

#[rstest]
#[case::localnet(localnet())]
fn test_error_update_state_non_epoch_boundary_epochs_is_timestamp(
#[case] hp: Box<dyn Network>,
) {
let input = hp.success_update_client_non_epoch_input();
let any: Any = input.header.try_into().unwrap();
let header = Header::try_from(any.clone()).unwrap();

let client = ParliaLightClient;
let client_id = ClientId::new(&client.client_type(), 1).unwrap();
let mut mock_consensus_state = BTreeMap::new();
let trusted_cs = ConsensusState {
current_validators_hash: input.trusted_current_validators_hash,
previous_validators_hash: input.trusted_previous_validators_hash,
..Default::default()
};
mock_consensus_state.insert(Height::new(0, input.trusted_height), trusted_cs.clone());

// Set fork spec is boundary timestamp
let mut boundary_fs = fork_spec_after_lorentz();
boundary_fs.height_or_timestamp =
HeightOrTimestamp::Time(header.eth_header().target.milli_timestamp());
let cs = ClientState {
chain_id: hp.network(),
ibc_store_address: hp.ibc_store_address(),
latest_height: Height::new(0, input.trusted_height),
fork_specs: vec![fork_spec_after_pascal(), boundary_fs],
..Default::default()
};
let ctx = MockClientReader {
client_state: Some(cs.clone()),
consensus_state: mock_consensus_state,
};
let err = client.update_client(&ctx, client_id, any).unwrap_err();
assert_err(err, "MissingForkHeightInBoundaryCalculation");
}

#[rstest]
#[case::localnet(localnet())]
fn test_success_update_state_non_epoch_update_fork_height(#[case] hp: Box<dyn Network>) {
let input = hp.success_update_client_non_epoch_input();
let any: Any = input.header.try_into().unwrap();
let header = Header::try_from(any.clone()).unwrap();

let client = ParliaLightClient;
let client_id = ClientId::new(&client.client_type(), 1).unwrap();
let mut mock_consensus_state = BTreeMap::new();
let trusted_cs = ConsensusState {
current_validators_hash: input.trusted_current_validators_hash,
previous_validators_hash: input.trusted_previous_validators_hash,
..Default::default()
};
mock_consensus_state.insert(Height::new(0, input.trusted_height), trusted_cs.clone());

// Set fork spec boundary timestamp is all[1]
let mut boundary_fs = fork_spec_after_lorentz();
boundary_fs.height_or_timestamp =
HeightOrTimestamp::Time(header.eth_header().all[1].milli_timestamp());
let cs = ClientState {
chain_id: hp.network(),
ibc_store_address: hp.ibc_store_address(),
latest_height: Height::new(0, input.trusted_height),
fork_specs: vec![fork_spec_after_pascal(), boundary_fs],
..Default::default()
};
let ctx = MockClientReader {
client_state: Some(cs.clone()),
consensus_state: mock_consensus_state,
};
let result = client.update_client(&ctx, client_id, any).unwrap();
let data = match result {
UpdateClientResult::UpdateState(data) => data,
_ => unreachable!("invalid client result"),
};
let new_client_state = ClientState::try_from(data.new_any_client_state).unwrap();

// update fork height
assert_eq!(
new_client_state.fork_specs[1].height_or_timestamp,
HeightOrTimestamp::Height(header.eth_header().all[1].number)
);
}

#[rstest]
#[case::localnet(localnet())]
fn test_success_update_state_continuous(#[case] hp: Box<dyn Network>) {
Expand Down Expand Up @@ -957,7 +1035,7 @@ mod test {
assert_err(
err,
&format!(
"UnexpectedTrustedHeight: {}",
"UnexpectedTrustedEpoch: {}",
trusted_height.revision_height()
),
);
Expand Down Expand Up @@ -1107,7 +1185,7 @@ mod test {
);
let ctx = MockClientReader {
client_state: Some(ClientState {
fork_specs: vec![after_pascal()],
fork_specs: vec![fork_spec_after_pascal(), fork_spec_after_lorentz()],
..Default::default()
}),
consensus_state: mock_consensus_state,
Expand Down Expand Up @@ -1178,7 +1256,7 @@ mod test {
let ctx = MockClientReader {
client_state: Some(ClientState {
chain_id: ChainId::new(9999),
fork_specs: vec![after_pascal()],
fork_specs: vec![fork_spec_after_pascal(), fork_spec_after_lorentz()],
..Default::default()
}),
consensus_state: mock_consensus_state.clone(),
Expand Down Expand Up @@ -1289,6 +1367,8 @@ mod test {
client_state.fork_specs = vec![ForkSpec {
height_or_timestamp: HeightOrTimestamp::Height(MINIMUM_HEIGHT_SUPPORTED - 1),
additional_header_item_count: 0,
epoch_length: 200,
max_turn_length: 9,
}];
(client_state, cons_state)
}));
Expand All @@ -1300,6 +1380,8 @@ mod test {
client_state.fork_specs = vec![ForkSpec {
height_or_timestamp: HeightOrTimestamp::Time(MINIMUM_TIMESTAMP_SUPPORTED - 1),
additional_header_item_count: 0,
epoch_length: 200,
max_turn_length: 9,
}];
(client_state, cons_state)
}));
Expand All @@ -1312,6 +1394,8 @@ mod test {
client_state.fork_specs = vec![ForkSpec {
height_or_timestamp: HeightOrTimestamp::Time(MINIMUM_TIMESTAMP_SUPPORTED),
additional_header_item_count: 0,
epoch_length: 200,
max_turn_length: 9,
}];
(client_state, cons_state)
}))
Expand Down
59 changes: 40 additions & 19 deletions light-client/src/client_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use parlia_ibc_proto::ibc::lightclients::parlia::v1::ClientState as RawClientSta

use crate::consensus_state::ConsensusState;
use crate::errors::Error;
use crate::fork_spec::ForkSpec;
use crate::fork_spec::{ForkSpec, HeightOrTimestamp};
use crate::header::Header;
use crate::misbehaviour::Misbehaviour;
use crate::misc::{new_height, Address, ChainId, Hash};
Expand Down Expand Up @@ -44,6 +44,7 @@ impl ClientState {
pub fn canonicalize(mut self) -> Self {
self.latest_height = new_height(self.chain_id.version(), 0);
self.frozen = false;
self.fork_specs = vec![];
self
}

Expand All @@ -56,12 +57,25 @@ impl ClientState {
&self,
now: Time,
trusted_consensus_state: &ConsensusState,
header: Header,
mut header: Header,
) -> Result<(ClientState, ConsensusState), Error> {
// Ensure header is valid
self.check_header(now, trusted_consensus_state, &header)?;

self.check_header(now, trusted_consensus_state, &mut header)?;
let mut new_client_state = self.clone();

// Update fork specs if timestamp
for fs in &mut new_client_state.fork_specs.iter_mut() {
if let HeightOrTimestamp::Time(ts) = fs.height_or_timestamp {
// second must be forks spec timestamp
if header.eth_header().all.len() >= 2 {
let h = &header.eth_header().all[1];
if ts <= h.milli_timestamp() {
fs.height_or_timestamp = HeightOrTimestamp::Height(h.number)
}
}
}
}

let header_height = header.height();
if new_client_state.latest_height < header_height {
new_client_state.latest_height = header_height;
Expand All @@ -82,14 +96,19 @@ impl ClientState {
now: Time,
h1_trusted_cs: &ConsensusState,
h2_trusted_cs: &ConsensusState,
misbehaviour: &Misbehaviour,
misbehaviour: &mut Misbehaviour,
) -> Result<ClientState, Error> {
self.check_header(now, h1_trusted_cs, &misbehaviour.header_1)?;
self.check_header(now, h2_trusted_cs, &misbehaviour.header_2)?;
self.check_header(now, h1_trusted_cs, &mut misbehaviour.header_1)?;
self.check_header(now, h2_trusted_cs, &mut misbehaviour.header_2)?;
Ok(self.clone().freeze())
}

fn check_header(&self, now: Time, cs: &ConsensusState, header: &Header) -> Result<(), Error> {
fn check_header(
&self,
now: Time,
cs: &ConsensusState,
header: &mut Header,
) -> Result<(), Error> {
// Ensure last consensus state is within the trusting period
let ts = header.timestamp()?;
validate_within_trusting_period(
Expand All @@ -110,7 +129,7 @@ impl ClientState {
}

// Ensure satisfying fork specs
header.verify_fork_rule(&self.fork_specs)?;
header.assign_fork_spec(&self.fork_specs)?;

// Ensure header is valid
header.verify(&self.chain_id, cs)
Expand Down Expand Up @@ -304,6 +323,8 @@ mod test {
ForkSpec {
height_or_timestamp: HeightOrTimestamp::Height(0),
additional_header_item_count: 1, // requestsHash
epoch_length: 200,
max_turn_length: 9,
}
}

Expand Down Expand Up @@ -398,8 +419,8 @@ mod test {
let h = hp.epoch_header();
let now = new_timestamp(h.milli_timestamp() - 1).unwrap();
cons_state.timestamp = new_timestamp(h.milli_timestamp()).unwrap();
let header = header_fn(0, &h, hp.epoch_header_rlp());
let err = cs.check_header(now, &cons_state, &header).unwrap_err();
let mut header = header_fn(0, &h, hp.epoch_header_rlp());
let err = cs.check_header(now, &cons_state, &mut header).unwrap_err();
match err {
Error::HeaderFromFuture(_, _, _) => {}
err => unreachable!("{:?}", err),
Expand All @@ -409,8 +430,8 @@ mod test {
let h = hp.epoch_header();
let now = new_timestamp(h.milli_timestamp() + 1).unwrap();
cons_state.timestamp = new_timestamp(h.milli_timestamp()).unwrap();
let header = header_fn(1, &h, hp.epoch_header_rlp());
let err = cs.check_header(now, &cons_state, &header).unwrap_err();
let mut header = header_fn(1, &h, hp.epoch_header_rlp());
let err = cs.check_header(now, &cons_state, &mut header).unwrap_err();
match err {
Error::UnexpectedHeaderRevision(n1, n2) => {
assert_eq!(cs.chain_id.version(), n1);
Expand All @@ -421,21 +442,21 @@ mod test {

// fail: verify_validator_set
let h = hp.epoch_header();
let header = header_fn(0, &h, hp.epoch_header_rlp());
let err = cs.check_header(now, &cons_state, &header).unwrap_err();
let mut header = header_fn(0, &h, hp.epoch_header_rlp());
let err = cs.check_header(now, &cons_state, &mut header).unwrap_err();
match err {
Error::UnexpectedUntrustedValidatorsHashInEpoch(h1, h2, _, _) => {
Error::UnexpectedUntrustedValidatorsHashInEpoch(h1, h2, _, _, _) => {
assert_eq!(h1.revision_height(), h.number - 1);
assert_eq!(h2.revision_height(), h.number);
assert_eq!(h2, h.number);
}
err => unreachable!("{:?}", err),
}

// fail: header.verify
let h = hp.epoch_header();
cons_state.current_validators_hash = Epoch::new(vec![h.coinbase.clone()].into(), 1).hash();
let header = header_fn(0, &h, hp.epoch_header_rlp());
let err = cs.check_header(now, &cons_state, &header).unwrap_err();
let mut header = header_fn(0, &h, hp.epoch_header_rlp());
let err = cs.check_header(now, &cons_state, &mut header).unwrap_err();
match err {
Error::UnexpectedCoinbase(number) => {
assert_eq!(number, h.number);
Expand Down
Loading
Loading