Skip to content

Commit 432ef3d

Browse files
authored
Merge pull request #76 from datachainlab/audit
Audit
2 parents 78d1349 + ca3459e commit 432ef3d

File tree

12 files changed

+594
-159
lines changed

12 files changed

+594
-159
lines changed

.github/workflows/ci.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,11 @@ jobs:
4141
with:
4242
command: test
4343
args: --release --features=std --manifest-path light-client/Cargo.toml
44+
- uses: actions-rs/cargo@v1
45+
name: unit-test-dev-test
46+
with:
47+
command: test
48+
args: --release --features=dev --manifest-path light-client/Cargo.toml --lib test::dev_test
49+
env:
50+
MINIMUM_TIMESTAMP_SUPPORTED: 1731495592
51+
MINIMUM_HEIGHT_SUPPORTED: 100

SPEC.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ interface ETHHeader {
140140
}
141141
```
142142
143-
## Misbehavior
143+
## misbehaviour
144144
145145
The `Misbehaviour` type is used for detecting misbehaviour and freezing the client - to prevent further packet flow - if applicable. Parlia client `Misbehaviour` consists of two headers at the same height both of which the light client would have considered valid.
146146
@@ -244,15 +244,15 @@ Primary verification according to BEP-126's finality rule involves:
244244
However, there may be cases where the VoteAttestation cannot directly determine the finality of the submitted header.
245245
In such cases, a valid descendant header is verified, which is included in the `headers` and can directly confirm its finality through VoteAttestation.
246246
247-
## Misbehavior predicate
247+
## misbehaviour predicate
248248
249-
The predicate will check if a submission contains evidence of Misbehavior.
249+
The predicate will check if a submission contains evidence of misbehaviour.
250250
If there are two different valid headers for the same height, the client will be frozen, preventing any further state updates.
251251
252252
```typescript
253253
function submitMisbehaviour(
254254
clientId: ClientId,
255-
misbehaviour: Misbehavior
255+
misbehaviour: misbehaviour
256256
): ClientState {
257257
// assert heights are equal
258258
assert(misbehaviour.header1.getHeight() == misbehaviour.header2.getHeight())

light-client/build.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,24 @@ fn main() {
88
writeln!(
99
file,
1010
"pub const BLOCKS_PER_EPOCH: u64 = {};",
11-
blocks_per_epoch
11+
blocks_per_epoch,
1212
)
1313
.unwrap();
1414
}
15+
16+
{
17+
use std::io::Write;
18+
let mut file = std::fs::File::create("src/header/hardfork.rs").unwrap();
19+
let minimum_time_stamp_supported =
20+
std::env::var("MINIMUM_TIMESTAMP_SUPPORTED").unwrap_or_else(|_| "0".to_string());
21+
let minimum_height_supported =
22+
std::env::var("MINIMUM_HEIGHT_SUPPORTED").unwrap_or_else(|_| "0".to_string());
23+
writeln!(
24+
file,
25+
"pub const MINIMUM_TIMESTAMP_SUPPORTED: u64 = {};\npub const MINIMUM_HEIGHT_SUPPORTED: u64 = {};",
26+
minimum_time_stamp_supported,
27+
minimum_height_supported
28+
)
29+
.unwrap();
30+
}
1531
}

light-client/src/client.rs

Lines changed: 130 additions & 29 deletions
Large diffs are not rendered by default.

light-client/src/client_state.rs

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use parlia_ibc_proto::ibc::lightclients::parlia::v1::ClientState as RawClientSta
1111
use crate::commitment::resolve_account;
1212
use crate::consensus_state::ConsensusState;
1313
use crate::errors::Error;
14+
use crate::header::hardfork::{MINIMUM_HEIGHT_SUPPORTED, MINIMUM_TIMESTAMP_SUPPORTED};
1415
use crate::header::Header;
1516
use crate::misbehaviour::Misbehaviour;
1617
use crate::misc::{new_height, Address, ChainId, Hash};
@@ -97,12 +98,23 @@ impl ClientState {
9798
}
9899

99100
fn check_header(&self, now: Time, cs: &ConsensusState, header: &Header) -> Result<(), Error> {
101+
// Ensure header has supported timestamp
102+
let ts = header.timestamp()?;
103+
104+
#[allow(clippy::absurd_extreme_comparisons)]
105+
if ts.as_unix_timestamp_secs() < MINIMUM_TIMESTAMP_SUPPORTED {
106+
return Err(Error::UnsupportedMinimumTimestamp(ts));
107+
}
108+
#[allow(clippy::absurd_extreme_comparisons)]
109+
if header.height().revision_height() < MINIMUM_HEIGHT_SUPPORTED {
110+
return Err(Error::UnsupportedMinimumHeight(header.height()));
111+
}
100112
// Ensure last consensus state is within the trusting period
101113
validate_within_trusting_period(
102114
now,
103115
self.trusting_period,
104116
self.max_clock_drift,
105-
header.timestamp()?,
117+
ts,
106118
cs.timestamp,
107119
)?;
108120

@@ -159,6 +171,13 @@ impl TryFrom<RawClientState> for ClientState {
159171

160172
let chain_id = ChainId::new(value.chain_id);
161173

174+
if chain_id.version() != raw_latest_height.revision_number {
175+
return Err(Error::UnexpectedLatestHeightRevision(
176+
chain_id.version(),
177+
raw_latest_height.revision_number,
178+
));
179+
}
180+
162181
let latest_height = new_height(
163182
raw_latest_height.revision_number,
164183
raw_latest_height.revision_height,
@@ -489,6 +508,19 @@ mod test {
489508
err => unreachable!("{:?}", err),
490509
}
491510

511+
cs.latest_height = Some(Height {
512+
revision_number: 1,
513+
revision_height: 0,
514+
});
515+
let err = ClientState::try_from(cs.clone()).unwrap_err();
516+
match err {
517+
Error::UnexpectedLatestHeightRevision(e1, e2) => {
518+
assert_eq!(e1, 0);
519+
assert_eq!(e2, 1);
520+
}
521+
err => unreachable!("{:?}", err),
522+
}
523+
492524
cs.latest_height = Some(Height::default());
493525
let err = ClientState::try_from(cs.clone()).unwrap_err();
494526
match err {
@@ -673,4 +705,69 @@ mod test {
673705
panic!("expected error");
674706
}
675707
}
708+
#[cfg(feature = "dev")]
709+
mod dev_test {
710+
use crate::client_state::ClientState;
711+
use crate::consensus_state::ConsensusState;
712+
use crate::errors::Error;
713+
use crate::fixture::localnet;
714+
use crate::header::eth_headers::ETHHeaders;
715+
use crate::header::hardfork::{MINIMUM_HEIGHT_SUPPORTED, MINIMUM_TIMESTAMP_SUPPORTED};
716+
use crate::header::Header;
717+
use crate::misc::new_timestamp;
718+
use parlia_ibc_proto::ibc::core::client::v1::Height;
719+
720+
#[test]
721+
fn test_supported_timestamp() {
722+
let header = Header::new(
723+
vec![1],
724+
ETHHeaders {
725+
target: localnet().previous_epoch_header(),
726+
all: vec![],
727+
},
728+
Height::default(),
729+
localnet().previous_epoch_header().epoch.unwrap(),
730+
localnet().epoch_header().epoch.unwrap(),
731+
);
732+
let cs = ClientState::default();
733+
let cons_state = ConsensusState::default();
734+
let err = cs
735+
.check_header(new_timestamp(0).unwrap(), &cons_state, &header)
736+
.unwrap_err();
737+
match err {
738+
Error::UnsupportedMinimumTimestamp(e1) => {
739+
assert_eq!(e1, header.timestamp().unwrap());
740+
}
741+
err => unreachable!("{:?}", err),
742+
}
743+
}
744+
745+
#[test]
746+
fn test_supported_height() {
747+
let mut header = Header::new(
748+
vec![1],
749+
ETHHeaders {
750+
target: localnet().previous_epoch_header(),
751+
all: vec![],
752+
},
753+
Height::default(),
754+
localnet().previous_epoch_header().epoch.unwrap(),
755+
localnet().epoch_header().epoch.unwrap(),
756+
);
757+
header.eth_header_mut().target.timestamp = MINIMUM_TIMESTAMP_SUPPORTED;
758+
header.eth_header_mut().target.number = MINIMUM_HEIGHT_SUPPORTED - 1;
759+
760+
let cs = ClientState::default();
761+
let cons_state = ConsensusState::default();
762+
let err = cs
763+
.check_header(new_timestamp(0).unwrap(), &cons_state, &header)
764+
.unwrap_err();
765+
match err {
766+
Error::UnsupportedMinimumHeight(e1) => {
767+
assert_eq!(e1, header.height());
768+
}
769+
err => unreachable!("{:?}", err),
770+
}
771+
}
772+
}
676773
}

light-client/src/errors.rs

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ pub enum Error {
3232
UnexpectedCommitmentSlot(Vec<u8>),
3333
ClientFrozen(ClientId),
3434
UnexpectedProofHeight(Height, Height),
35+
UnexpectedRevisionHeight(u64),
3536

3637
// ConsensusState error
3738
AccountNotFound(Address),
@@ -50,6 +51,8 @@ pub enum Error {
5051
UnexpectedValidatorsHashSize(Vec<u8>),
5152

5253
// Header error
54+
UnsupportedMinimumTimestamp(Time),
55+
UnsupportedMinimumHeight(Height),
5356
MissingPreviousValidators(BlockNumber),
5457
MissingCurrentValidators(BlockNumber),
5558
OutOfTrustingPeriod(Time, Time),
@@ -60,6 +63,7 @@ pub enum Error {
6063
UnexpectedTrustedHeight(BlockNumber, BlockNumber),
6164
EmptyHeader,
6265
UnexpectedHeaderRevision(u64, u64),
66+
UnexpectedLatestHeightRevision(u64, u64),
6367
UnexpectedSignature(BlockNumber, signature::Error),
6468
MissingVanityInExtraData(BlockNumber, usize, usize),
6569
MissingSignatureInExtraData(BlockNumber, usize, usize),
@@ -80,11 +84,10 @@ pub enum Error {
8084
MissingTurnLengthInEpochBlock(BlockNumber),
8185
MissingEpochInfoInEpochBlock(BlockNumber),
8286
MissingNextValidatorSet(BlockNumber),
83-
MissingCurrentValidatorSet(BlockNumber),
8487
UnexpectedPreviousValidatorsHash(Height, Height, Hash, Hash),
8588
UnexpectedCurrentValidatorsHash(Height, Height, Hash, Hash),
8689
InvalidVerifyingHeaderLength(BlockNumber, usize),
87-
InsufficientTrustedValidatorsInUntrustedValidators(Hash, usize, usize),
90+
InsufficientHonestValidator(Hash, usize, usize),
8891
MissingValidatorToVerifySeal(BlockNumber),
8992
MissingValidatorToVerifyVote(BlockNumber),
9093
UnexpectedNextCheckpointHeader(BlockNumber, BlockNumber),
@@ -94,6 +97,7 @@ pub enum Error {
9497
UnexpectedDifficultyNoTurn(BlockNumber, u64, usize),
9598
UnexpectedUntrustedValidatorsHashInEpoch(Height, Height, Hash, Hash),
9699
UnexpectedCurrentValidatorsHashInEpoch(Height, Height, Hash, Hash),
100+
UnexpectedUntrustedValidators(BlockNumber, BlockNumber),
97101

98102
// Vote attestation
99103
UnexpectedTooManyHeadersToFinalize(BlockNumber, usize),
@@ -163,6 +167,9 @@ impl core::fmt::Display for Error {
163167
Error::UnexpectedHeaderRevision(e1, e2) => {
164168
write!(f, "UnexpectedHeaderRevision: {} {}", e1, e2)
165169
}
170+
Error::UnexpectedLatestHeightRevision(e1, e2) => {
171+
write!(f, "UnexpectedLatestHeightRevision: {} {}", e1, e2)
172+
}
166173
Error::UnexpectedSignature(e1, e2) => write!(f, "UnexpectedSignature: {} {}", e1, e2),
167174
Error::MissingVanityInExtraData(e1, e2, e3) => {
168175
write!(f, "MissingVanityInExtraData: {} {} {}", e1, e2, e3)
@@ -309,19 +316,12 @@ impl core::fmt::Display for Error {
309316
Error::UnexpectedVoteRelation(e1, e2, e3) => {
310317
write!(f, "UnexpectedVoteRelation : {} {} {:?}", e1, e2, e3)
311318
}
312-
Error::InsufficientTrustedValidatorsInUntrustedValidators(e1, e2, e3) => {
313-
write!(
314-
f,
315-
"InsufficientTrustedValidatorsInUntrustedValidators : {:?} {} {}",
316-
e1, e2, e3
317-
)
319+
Error::InsufficientHonestValidator(e1, e2, e3) => {
320+
write!(f, "InsufficientHonestValidator : {:?} {} {}", e1, e2, e3)
318321
}
319322
Error::MissingNextValidatorSet(e1) => {
320323
write!(f, "MissingNextValidatorSet : {}", e1)
321324
}
322-
Error::MissingCurrentValidatorSet(e1) => {
323-
write!(f, "MissingCurrentValidatorSet : {}", e1)
324-
}
325325
Error::MissingValidatorToVerifySeal(e1) => {
326326
write!(f, "MissingValidatorToVerifySeal : {:?}", e1)
327327
}
@@ -372,6 +372,18 @@ impl core::fmt::Display for Error {
372372
e1, e2, e3, e4
373373
)
374374
}
375+
Error::UnexpectedUntrustedValidators(e1, e2) => {
376+
write!(f, "UnexpectedUntrustedValidators : {} {}", e1, e2)
377+
}
378+
Error::UnsupportedMinimumTimestamp(e1) => {
379+
write!(f, "UnsupportedMinimumTimestamp : {:?}", e1)
380+
}
381+
Error::UnsupportedMinimumHeight(e1) => {
382+
write!(f, "UnsupportedMinimumHeight : {:?}", e1)
383+
}
384+
Error::UnexpectedRevisionHeight(e1) => {
385+
write!(f, "UnexpectedRevisionHeight : {}", e1)
386+
}
375387
}
376388
}
377389
}

0 commit comments

Comments
 (0)