@@ -195,6 +195,11 @@ where
195
195
. predicates
196
196
. is_monotonic_bft_time( untrusted. signed_header. header. time, trusted. header_time) ) ;
197
197
198
+ // Check that the chain-id of the untrusted block matches that of the trusted state
199
+ verdict ! ( self
200
+ . predicates
201
+ . is_matching_chain_id( & untrusted. signed_header. header. chain_id, trusted. chain_id) ) ;
202
+
198
203
let trusted_next_height = trusted. height . increment ( ) ;
199
204
200
205
if untrusted. height ( ) == trusted_next_height {
@@ -287,3 +292,68 @@ where
287
292
/// The default production implementation of the [`PredicateVerifier`].
288
293
pub type ProdVerifier =
289
294
PredicateVerifier < ProdPredicates , ProdVotingPowerCalculator , ProdCommitValidator , ProdHasher > ;
295
+
296
+ #[ cfg( test) ]
297
+ mod tests {
298
+ use alloc:: { borrow:: ToOwned , string:: ToString } ;
299
+ use core:: { ops:: Sub , time:: Duration } ;
300
+
301
+ use tendermint:: Time ;
302
+ use tendermint_testgen:: { light_block:: LightBlock as TestgenLightBlock , Generator } ;
303
+
304
+ use crate :: {
305
+ errors:: VerificationErrorDetail , options:: Options , types:: LightBlock , ProdVerifier ,
306
+ Verdict , Verifier ,
307
+ } ;
308
+
309
+ #[ test]
310
+ fn test_verification_failure_on_chain_id_mismatch ( ) {
311
+ let now = Time :: now ( ) ;
312
+
313
+ // Create a default light block with a valid chain-id for height `1` with a timestamp 20
314
+ // secs before now (to be treated as trusted state)
315
+ let light_block_1: LightBlock = TestgenLightBlock :: new_default_with_time_and_chain_id (
316
+ "chain-1" . to_owned ( ) ,
317
+ now. sub ( Duration :: from_secs ( 20 ) ) . unwrap ( ) ,
318
+ 1u64 ,
319
+ )
320
+ . generate ( )
321
+ . unwrap ( )
322
+ . into ( ) ;
323
+
324
+ // Create another default block with a different chain-id for height `2` with a timestamp 10
325
+ // secs before now (to be treated as untrusted state)
326
+ let light_block_2: LightBlock = TestgenLightBlock :: new_default_with_time_and_chain_id (
327
+ "forged-chain" . to_owned ( ) ,
328
+ now. sub ( Duration :: from_secs ( 10 ) ) . unwrap ( ) ,
329
+ 2u64 ,
330
+ )
331
+ . generate ( )
332
+ . unwrap ( )
333
+ . into ( ) ;
334
+
335
+ let vp = ProdVerifier :: default ( ) ;
336
+ let opt = Options {
337
+ trust_threshold : Default :: default ( ) ,
338
+ trusting_period : Duration :: from_secs ( 60 ) ,
339
+ clock_drift : Default :: default ( ) ,
340
+ } ;
341
+
342
+ let verdict = vp. verify (
343
+ light_block_2. as_untrusted_state ( ) ,
344
+ light_block_1. as_trusted_state ( ) ,
345
+ & opt,
346
+ Time :: now ( ) ,
347
+ ) ;
348
+
349
+ match verdict {
350
+ Verdict :: Invalid ( VerificationErrorDetail :: ChainIdMismatch ( e) ) => {
351
+ let chain_id_1 = light_block_1. signed_header . header . chain_id ;
352
+ let chain_id_2 = light_block_2. signed_header . header . chain_id ;
353
+ assert_eq ! ( e. got, chain_id_2. to_string( ) ) ;
354
+ assert_eq ! ( e. expected, chain_id_1. to_string( ) ) ;
355
+ } ,
356
+ v => panic ! ( "expected ChainIdMismatch error, got: {:?}" , v) ,
357
+ }
358
+ }
359
+ }
0 commit comments