1use std::{convert::TryInto, str::FromStr, sync::Arc, time::Duration};
3
4use anyhow::anyhow;
5use crossbeam::channel;
6use rand::{rngs::SysRng, TryRng};
7use sha2::{Digest, Sha256};
8use slog::{debug, error, info};
9use tendermint::merkle::HASH_SIZE;
10use tendermint_light_client::{
11 builder::LightClientBuilder,
12 components::{self, io::AtHeight, verifier::PredicateVerifier},
13 instance::Instance,
14 light_client,
15 operations::{ProdCommitValidator, ProvidedVotingPowerCalculator},
16 store::LightStore,
17 types::{
18 Hash as TMHash, LightBlock as TMLightBlock, PeerId, Status, Time, TrustThreshold,
19 TrustedBlockState,
20 },
21 verifier::{predicates::ProdPredicates, Verdict, Verifier as TMVerifier},
22};
23
24use crate::{
25 common::{logger::get_logger, namespace::Namespace, process, time, version::Version},
26 consensus::{
27 beacon::EpochTime,
28 registry::METHOD_PROVE_FRESHNESS,
29 roothash::Header,
30 state::ConsensusState,
31 tendermint::{
32 chain_id, decode_light_block, merkle, state_root_from_header,
33 verifier::{
34 clock::InsecureClock,
35 io::Io,
36 store::LruStore,
37 types::{Command, Nonce, NONCE_SIZE},
38 },
39 LightBlockMeta,
40 },
41 transaction::{Proof, SignedTransaction, Transaction},
42 verifier::{self, verify_state_freshness, Error, TrustRoot},
43 BlockMetadata, Event, LightBlock, HEIGHT_LATEST, METHOD_META,
44 },
45 future::block_on,
46 host::Host,
47 protocol::Protocol,
48 storage::mkvs::{Root, RootType},
49 types::{Body, EventKind, HostFetchConsensusEventsRequest, HostFetchConsensusEventsResponse},
50};
51
52use self::{
53 cache::Cache,
54 handle::Handle,
55 store::{TrustedState, TrustedStateStore},
56};
57
58mod cache;
60mod clock;
61mod handle;
62mod io;
63mod noop;
64mod predicates;
65mod signature;
66mod store;
67mod types;
68
69pub use noop::NopVerifier;
71
72const MAX_INITIALIZATION_RETRIES: usize = 3;
74
75const TRUSTED_STATE_SAVE_INTERVAL: u64 = 128;
77
78pub struct Verifier {
80 logger: slog::Logger,
81 protocol: Arc<Protocol>,
82 tokio_runtime: tokio::runtime::Handle,
83 runtime_version: Version,
84 runtime_id: Namespace,
85 chain_context: String,
86 trust_root: TrustRoot,
87 command_sender: channel::Sender<Command>,
88 command_receiver: channel::Receiver<Command>,
89 trusted_state_store: TrustedStateStore,
90}
91
92impl Verifier {
93 pub fn new(
95 protocol: Arc<Protocol>,
96 tokio_runtime: tokio::runtime::Handle,
97 trust_root: TrustRoot,
98 runtime_id: Namespace,
99 chain_context: String,
100 ) -> Self {
101 let logger = get_logger("consensus/cometbft/verifier");
102 let (command_sender, command_receiver) = channel::unbounded();
103 let runtime_version = protocol.get_config().version;
104 let trusted_state_store =
105 TrustedStateStore::new(runtime_id, chain_context.clone(), protocol.clone());
106
107 assert_eq!(
108 trust_root.runtime_id, runtime_id,
109 "trust root must have the same runtime id"
110 );
111
112 Self {
113 logger,
114 protocol,
115 tokio_runtime,
116 runtime_version,
117 runtime_id,
118 chain_context,
119 trust_root,
120 command_sender,
121 command_receiver,
122 trusted_state_store,
123 }
124 }
125
126 pub fn handle(&self) -> impl verifier::Verifier {
128 Handle {
129 protocol: self.protocol.clone(),
130 command_sender: self.command_sender.clone(),
131 }
132 }
133
134 fn verify_to_target(
135 &self,
136 height: u64,
137 cache: &mut Cache,
138 instance: &mut Instance,
139 ) -> Result<TMLightBlock, Error> {
140 let verified_block = match height {
141 HEIGHT_LATEST => instance.light_client.verify_to_highest(&mut instance.state),
142 _ => instance
143 .light_client
144 .verify_to_target(height.try_into().unwrap(), &mut instance.state),
145 }
146 .map_err(|err| Error::VerificationFailed(err.into()))?;
147
148 instance.state.verification_trace.clear();
150
151 cache.update_verified_block(&verified_block);
152 self.update_insecure_posix_time(&verified_block);
153
154 Ok(verified_block)
155 }
156
157 fn sync(&self, cache: &mut Cache, instance: &mut Instance, height: u64) -> Result<(), Error> {
158 if height != HEIGHT_LATEST
159 && (height < cache.last_verified_height
160 || height < cache.latest_known_height().unwrap_or(0))
161 {
162 return Ok(());
164 }
165 self.verify_to_target(height, cache, instance)?;
166 Ok(())
167 }
168
169 fn latest_consensus_state(
170 &self,
171 cache: &mut Cache,
172 instance: &mut Instance,
173 ) -> Result<ConsensusState, Error> {
174 let height = self.latest_consensus_height(cache)?;
176 let state_root = self.state_root_from_metadata(cache, instance, height)?;
177
178 Ok(ConsensusState::from_protocol(
179 self.protocol.clone(),
180 state_root.version,
181 state_root,
182 ))
183 }
184
185 fn latest_consensus_height(&self, cache: &Cache) -> Result<u64, Error> {
186 let height = cache.latest_known_height().ok_or(Error::Internal)?;
187 Ok(height)
188 }
189
190 fn consensus_state_at(
191 &self,
192 cache: &mut Cache,
193 instance: &mut Instance,
194 height: u64,
195 ) -> Result<ConsensusState, Error> {
196 let state_root = match self.verify_to_target(height, cache, instance) {
199 Ok(verified_block) => state_root_from_header(&verified_block.signed_header),
200 Err(_) => self.state_root_from_metadata(cache, instance, height - 1)?,
201 };
202
203 Ok(ConsensusState::from_protocol(
204 self.protocol.clone(),
205 state_root.version + 1,
206 state_root,
207 ))
208 }
209
210 fn verify_consensus_block(
211 &self,
212 cache: &mut Cache,
213 instance: &mut Instance,
214 consensus_block: LightBlock,
215 ) -> Result<LightBlockMeta, Error> {
216 let lb_height = consensus_block.height;
218 let untrusted_block =
219 decode_light_block(consensus_block).map_err(Error::VerificationFailed)?;
220 let untrusted_header = untrusted_block
221 .signed_header
222 .as_ref()
223 .ok_or_else(|| Error::VerificationFailed(anyhow!("missing signed header")))?;
224
225 let height = untrusted_header.header().height.value();
228 if height != lb_height {
229 return Err(Error::VerificationFailed(anyhow!(
230 "inconsistent light block/header height"
231 )));
232 }
233 let verified_block = self.verify_to_target(height, cache, instance)?;
234
235 if untrusted_header.header() != verified_block.signed_header.header() {
237 return Err(Error::VerificationFailed(anyhow!("header mismatch")));
238 }
239
240 Ok(untrusted_block)
241 }
242
243 fn verify_freshness_with_rak(
245 &self,
246 state: &ConsensusState,
247 cache: &Cache,
248 ) -> Result<(), Error> {
249 let identity = if let Some(identity) = self.protocol.get_identity() {
250 identity
251 } else {
252 return Ok(());
253 };
254
255 verify_state_freshness(
256 state,
257 identity,
258 &self.runtime_id,
259 &self.runtime_version,
260 &cache.host_node_id,
261 )
262 }
263
264 fn verify_freshness_with_proof(
273 &self,
274 instance: &mut Instance,
275 cache: &mut Cache,
276 ) -> Result<(), Error> {
277 info!(
278 self.logger,
279 "Verifying state freshness using prove freshness transaction"
280 );
281
282 let mut nonce = [0u8; NONCE_SIZE];
284 SysRng.try_fill_bytes(&mut nonce).unwrap();
285
286 let io = Io::new(&self.protocol);
288 let stwp = io.fetch_freshness_proof(&nonce).map_err(|err| {
289 Error::FreshnessVerificationFailed(anyhow!("failed to fetch freshness proof: {}", err))
290 })?;
291
292 let tx = self.verify_transaction(cache, instance, &stwp.signed_tx, &stwp.proof)?;
294
295 if tx.method != METHOD_PROVE_FRESHNESS {
297 return Err(Error::FreshnessVerificationFailed(anyhow!(
298 "invalid method name"
299 )));
300 }
301
302 let tx_nonce: Nonce = cbor::from_value(tx.body).map_err(|err| {
303 Error::FreshnessVerificationFailed(anyhow!("failed to decode nonce: {}", err))
304 })?;
305 match nonce.cmp(&tx_nonce) {
306 std::cmp::Ordering::Equal => (),
307 _ => return Err(Error::FreshnessVerificationFailed(anyhow!("invalid nonce"))),
308 }
309
310 info!(self.logger, "State freshness successfully verified");
311
312 Ok(())
313 }
314
315 fn verify_transaction(
316 &self,
317 cache: &mut Cache,
318 instance: &mut Instance,
319 signed_tx: &SignedTransaction,
320 proof: &Proof,
321 ) -> Result<Transaction, Error> {
322 if !signed_tx.verify(&self.chain_context) {
324 return Err(Error::TransactionVerificationFailed(anyhow!(
325 "failed to verify the signature"
326 )));
327 }
328
329 let verified_block = self
331 .verify_to_target(proof.height, cache, instance)
332 .map_err(|err| {
333 Error::TransactionVerificationFailed(anyhow!("failed to fetch the block: {}", err))
334 })?;
335
336 let header = verified_block.signed_header.header;
337 if header.height.value() != proof.height {
338 return Err(Error::TransactionVerificationFailed(anyhow!(
339 "invalid block"
340 )));
341 }
342
343 let root_hash = header
344 .data_hash
345 .ok_or_else(|| Error::TransactionVerificationFailed(anyhow!("root hash not found")))?;
346 let root_hash = match root_hash {
347 TMHash::Sha256(hash) => hash,
348 TMHash::None => {
349 return Err(Error::TransactionVerificationFailed(anyhow!(
350 "root hash not found"
351 )));
352 }
353 };
354
355 let digest = Sha256::digest(cbor::to_vec(signed_tx.clone()));
357 let mut tx_hash = [0u8; HASH_SIZE];
358 tx_hash.copy_from_slice(&digest);
359
360 let merkle_proof: merkle::Proof = cbor::from_slice(&proof.raw_proof).map_err(|err| {
362 Error::TransactionVerificationFailed(anyhow!("failed to decode Merkle proof: {}", err))
363 })?;
364
365 merkle_proof.verify(root_hash, tx_hash).map_err(|err| {
366 Error::TransactionVerificationFailed(anyhow!("failed to verify Merkle proof: {}", err))
367 })?;
368
369 let tx: Transaction = cbor::from_slice(signed_tx.blob.as_slice()).map_err(|err| {
371 Error::TransactionVerificationFailed(anyhow!("failed to decode transaction: {}", err))
372 })?;
373
374 Ok(tx)
375 }
376
377 fn state_root_from_metadata(
379 &self,
380 cache: &mut Cache,
381 instance: &mut Instance,
382 height: u64,
383 ) -> Result<Root, Error> {
384 debug!(
385 self.logger,
386 "Fetching state root from block metadata transaction"
387 );
388
389 let io = Io::new(&self.protocol);
391 let stwp = io.fetch_block_metadata(height).map_err(|err| {
392 Error::StateRoot(anyhow!(
393 "failed to fetch block metadata transaction: {}",
394 err
395 ))
396 })?;
397
398 let tx = self.verify_transaction(cache, instance, &stwp.signed_tx, &stwp.proof)?;
400
401 if tx.method != METHOD_META {
402 return Err(Error::StateRoot(anyhow!("invalid method name")));
403 }
404
405 let meta: BlockMetadata = cbor::from_value(tx.body).map_err(|err| {
406 Error::StateRoot(anyhow!(
407 "failed to decode block metadata transaction: {}",
408 err
409 ))
410 })?;
411
412 Ok(Root {
413 namespace: Namespace::default(),
414 version: height,
415 root_type: RootType::State,
416 hash: meta.state_root,
417 })
418 }
419
420 fn verify(
421 &self,
422 cache: &mut Cache,
423 instance: &mut Instance,
424 consensus_block: LightBlock,
425 runtime_header: Header,
426 epoch: EpochTime,
427 ) -> Result<ConsensusState, Error> {
428 predicates::verify_namespace(self.runtime_id, &runtime_header)?;
430 predicates::verify_round_advance(cache, &runtime_header, &consensus_block, epoch)?;
431 predicates::verify_consensus_advance(cache, &consensus_block)?;
432
433 let height = consensus_block.height;
435 let consensus_block = self.verify_consensus_block(cache, instance, consensus_block)?;
436
437 predicates::verify_time(&runtime_header, &consensus_block)?;
439
440 let state = self.consensus_state_at(cache, instance, height)?;
442
443 if let Some((state_root, state_epoch)) =
445 cache.verified_state_roots.get(&runtime_header.round)
446 {
447 if state_root == &runtime_header.state_root
448 && state_epoch == &epoch
449 && epoch == cache.last_verified_epoch
450 {
451 cache.last_verified_height = height;
455 cache.last_verified_round = runtime_header.round;
456
457 return Ok(state);
458 }
459
460 }
462
463 let next_state = self.consensus_state_at(cache, instance, height + 1)?;
467
468 predicates::verify_state_root(&next_state, &runtime_header)?;
470 predicates::verify_epoch(&next_state, epoch)?;
471
472 if cache.last_verified_epoch != epoch {
475 let latest_state = self.latest_consensus_state(cache, instance)?;
476 self.verify_freshness_with_rak(&latest_state, cache)?;
477 }
478
479 cache
481 .verified_state_roots
482 .put(runtime_header.round, (runtime_header.state_root, epoch));
483
484 cache.last_verified_height = height;
486 cache.last_verified_round = runtime_header.round;
487 cache.last_verified_epoch = epoch;
488
489 Ok(state)
490 }
491
492 fn verify_for_query(
493 &self,
494 cache: &mut Cache,
495 instance: &mut Instance,
496 consensus_block: LightBlock,
497 runtime_header: Header,
498 epoch: EpochTime,
499 ) -> Result<ConsensusState, Error> {
500 predicates::verify_namespace(self.runtime_id, &runtime_header)?;
502
503 let height = consensus_block.height;
505 let consensus_block = self.verify_consensus_block(cache, instance, consensus_block)?;
506
507 predicates::verify_time(&runtime_header, &consensus_block)?;
509
510 let state = self.consensus_state_at(cache, instance, height)?;
512
513 if let Some((state_root, state_epoch)) =
515 cache.verified_state_roots.get(&runtime_header.round)
516 {
517 if state_root == &runtime_header.state_root && state_epoch == &epoch {
518 return Ok(state);
520 }
521
522 }
524
525 let next_state = self.consensus_state_at(cache, instance, height + 1)?;
529
530 predicates::verify_state_root(&next_state, &runtime_header)?;
532 predicates::verify_epoch(&next_state, epoch)?;
533
534 cache
536 .verified_state_roots
537 .put(runtime_header.round, (runtime_header.state_root, epoch));
538
539 Ok(state)
540 }
541
542 fn events_at(&self, height: u64, kind: EventKind) -> Result<Vec<Event>, Error> {
543 let result = self
544 .protocol
545 .call_host(Body::HostFetchConsensusEventsRequest(
546 HostFetchConsensusEventsRequest { height, kind },
547 ))
548 .map_err(|err| Error::VerificationFailed(err.into()))?;
549 match result {
552 Body::HostFetchConsensusEventsResponse(HostFetchConsensusEventsResponse { events }) => {
553 Ok(events)
554 }
555 _ => Err(Error::VerificationFailed(anyhow!("bad response from host"))),
556 }
557 }
558
559 fn update_insecure_posix_time(&self, verified_block: &TMLightBlock) {
560 time::update_insecure_posix_time(
564 verified_block
565 .signed_header
566 .header
567 .time
568 .duration_since(Time::unix_epoch())
569 .unwrap()
570 .as_secs()
571 .try_into()
572 .unwrap(),
573 );
574 }
575
576 pub fn start(self) {
578 std::thread::spawn(move || {
579 let _guard = self.tokio_runtime.enter(); let logger = get_logger("consensus/cometbft/verifier");
582 info!(logger, "Starting consensus verifier");
583
584 for retry in 1..=MAX_INITIALIZATION_RETRIES {
589 let result =
591 match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| self.run())) {
592 Ok(result) => result,
593 Err(_) => {
594 error!(logger, "Consensus verifier aborted");
595 process::abort();
596 }
597 };
598
599 match result {
601 Ok(_) => {}
602 Err(err @ Error::Builder(_))
603 | Err(err @ Error::TrustedStateLoadingFailed)
604 | Err(err @ Error::ChainContextTransitionFailed(_)) => {
605 error!(logger, "Consensus verifier failed to initialize, retrying";
606 "err" => %err,
607 "retry" => retry,
608 );
609 }
610 Err(err) => {
611 error!(logger, "Consensus verifier terminated, aborting";
613 "err" => %err,
614 );
615 process::abort();
616 }
617 }
618
619 std::thread::sleep(Duration::from_secs(1));
621 }
622
623 error!(logger, "Failed to start consensus verifier, aborting");
624 process::abort();
625 });
626 }
627
628 fn run(&self) -> Result<(), Error> {
629 let options = light_client::Options {
631 trust_threshold: Default::default(),
632 trusting_period: Duration::from_secs(3600 * 24 * 365 * 10), clock_drift: Duration::from_secs(60),
636 };
637
638 let peer_id = PeerId::new([0; 20]);
640 let clock = Box::new(InsecureClock);
641 let verifier = Box::new(PredicateVerifier::new(
642 ProdPredicates,
643 ProvidedVotingPowerCalculator::<signature::DomSepVerifier>::default(),
644 ProdCommitValidator,
645 ));
646 let io = Box::new(Io::new(&self.protocol));
647
648 info!(self.logger, "Loading trusted state");
651 let trusted_state = self
652 .trusted_state_store
653 .load(self.runtime_version, &self.trust_root);
654
655 let trusted_state: TrustedState = match trusted_state {
656 Ok(state) => state,
657 Err(err) => {
658 error!(self.logger, "failed to load trusted state (if running in SGX mode, check if the CPU had changed; if yes, wipe 'worker-local-storage.badger.db' and restart the node)");
659 return Err(err);
660 }
661 };
662
663 info!(self.logger, "Checking chain context change");
666 let trusted_state = self.handle_chain_context_change(
667 trusted_state,
668 verifier.as_ref(),
669 clock.as_ref(),
670 io.as_ref(),
671 )?;
672
673 let mut store = Box::new(LruStore::new(
675 512,
676 trusted_state.trust_root.height.try_into().unwrap(),
677 ));
678 for lb in trusted_state.trusted_blocks {
679 store.insert(lb.into(), Status::Trusted);
680 }
681 let trust_root = trusted_state.trust_root;
682
683 let builder = LightClientBuilder::custom(
684 peer_id,
685 options,
686 store,
687 io,
688 clock,
689 verifier,
690 Box::new(components::scheduler::basic_bisecting_schedule),
691 Box::new(ProdPredicates),
692 );
693
694 let mut instance = builder
695 .trust_primary_at(
696 trust_root.height.try_into().unwrap(),
697 TMHash::from_str(&trust_root.hash.to_uppercase()).unwrap(),
698 )
699 .map_err(|err| Error::Builder(err.into()))?
700 .build();
701
702 info!(self.logger, "Consensus verifier initialized";
703 "trust_root_height" => trust_root.height,
704 "trust_root_hash" => ?trust_root.hash,
705 "trust_root_runtime_id" => ?trust_root.runtime_id,
706 "trust_root_chain_context" => ?trust_root.chain_context,
707 );
708
709 let host_node_id =
710 block_on(self.protocol.identity()).expect("host should provide a node identity");
711
712 let mut cache = Cache::new(host_node_id);
713
714 let verified_block = self.verify_to_target(HEIGHT_LATEST, &mut cache, &mut instance)?;
717
718 self.trusted_state_store
719 .save(self.runtime_version, &*instance.state.light_store);
720
721 let mut last_saved_verified_block_height =
722 verified_block.signed_header.header.height.value();
723
724 info!(self.logger, "Consensus verifier synced";
725 "latest_height" => cache.latest_known_height(),
726 );
727
728 self.verify_freshness_with_proof(&mut instance, &mut cache)?;
732
733 loop {
735 let command = self.command_receiver.recv().map_err(|_| Error::Internal)?;
736
737 match command {
738 Command::Synchronize(height, sender) => {
739 sender
740 .send(self.sync(&mut cache, &mut instance, height))
741 .map_err(|_| Error::Internal)?;
742 }
743 Command::Verify(consensus_block, runtime_header, epoch, sender, false) => {
744 sender
745 .send(self.verify(
746 &mut cache,
747 &mut instance,
748 consensus_block,
749 *runtime_header,
750 epoch,
751 ))
752 .map_err(|_| Error::Internal)?;
753 }
754 Command::Verify(consensus_block, runtime_header, epoch, sender, true) => {
755 sender
756 .send(self.verify_for_query(
757 &mut cache,
758 &mut instance,
759 consensus_block,
760 *runtime_header,
761 epoch,
762 ))
763 .map_err(|_| Error::Internal)?;
764 }
765 Command::LatestState(sender) => {
766 sender
767 .send(self.latest_consensus_state(&mut cache, &mut instance))
768 .map_err(|_| Error::Internal)?;
769 }
770 Command::StateAt(height, sender) => {
771 sender
772 .send(self.consensus_state_at(&mut cache, &mut instance, height))
773 .map_err(|_| Error::Internal)?;
774 }
775 Command::LatestHeight(sender) => {
776 sender
777 .send(self.latest_consensus_height(&cache))
778 .map_err(|_| Error::Internal)?;
779 }
780 Command::EventsAt(height, kind, sender) => {
781 sender
782 .send(self.events_at(height, kind))
783 .map_err(|_| Error::Internal)?;
784 }
785 }
786
787 if let Some(last_verified_block) = cache.last_verified_block.as_ref() {
789 let last_height = last_verified_block.signed_header.header.height.into();
790 if last_height - last_saved_verified_block_height > TRUSTED_STATE_SAVE_INTERVAL {
791 self.trusted_state_store
792 .save(self.runtime_version, &*instance.state.light_store);
793 last_saved_verified_block_height = last_height;
794 }
795 }
796 }
797 }
798
799 fn handle_chain_context_change(
800 &self,
801 mut trusted_state: TrustedState,
802 verifier: &impl TMVerifier,
803 clock: &impl components::clock::Clock,
804 io: &Io,
805 ) -> Result<TrustedState, Error> {
806 let host_info = self.protocol.get_host_info();
807
808 if trusted_state.trust_root.chain_context == host_info.consensus_chain_context {
810 info!(self.logger, "Consensus chain context hasn't changed");
811 return Ok(trusted_state);
812 }
813 info!(self.logger, "Consensus chain context has changed");
814
815 let trusted_block: TMLightBlock = trusted_state
819 .trusted_blocks
820 .pop()
821 .ok_or_else(|| {
822 Error::ChainContextTransitionFailed(anyhow!(
823 "cannot transition from embedded trust root"
824 ))
825 })?
826 .into();
827
828 let height = io
832 .fetch_genesis_height()
833 .map_err(|err| Error::ChainContextTransitionFailed(err.into()))?;
834 let height = AtHeight::At(height.try_into().unwrap());
835 let untrusted_block = components::io::Io::fetch_light_block(io, height)
836 .map_err(|err| Error::ChainContextTransitionFailed(err.into()))?;
837
838 if untrusted_block.signed_header.header.last_block_id.is_some() {
839 return Err(Error::ChainContextTransitionFailed(anyhow!(
840 "invalid genesis block"
841 )));
842 }
843
844 let untrusted = untrusted_block.as_untrusted_state();
845
846 let header = trusted_block.signed_header.header;
854 let height = header.height;
855 let height = if height.increment() != untrusted.height() {
856 height
857 } else {
858 height
859 .value()
860 .checked_sub(1)
861 .ok_or_else(|| Error::ChainContextTransitionFailed(anyhow!("height underflow")))?
862 .try_into()
863 .unwrap()
864 };
865
866 let trusted = TrustedBlockState {
867 header_time: header.time,
868 height,
869 next_validators: &trusted_block.validators,
870 next_validators_hash: header.validators_hash,
871 chain_id: &chain_id(&host_info.consensus_chain_context),
873 };
874
875 let options = light_client::Options {
877 trust_threshold: TrustThreshold::TWO_THIRDS,
878 trusting_period: Duration::from_secs(3600 * 24 * 365 * 10), clock_drift: Duration::from_secs(60),
880 };
881 let now = clock.now();
882
883 let verdict = verifier.verify_update_header(untrusted, trusted, &options, now);
884
885 match verdict {
886 Verdict::Success => (),
887 Verdict::NotEnoughTrust(tally) => {
888 info!(
889 self.logger,
890 "Not enough trust to accept new chain context";
891 "log_event" => "consensus/cometbft/verifier/chain_context/no_trust",
892 "tally" => ?tally,
893 );
894 return Err(Error::ChainContextTransitionFailed(anyhow!(
895 "not enough trust"
896 )));
897 }
898 Verdict::Invalid(e) => {
899 info!(
900 self.logger,
901 "Failed to accept new chain context";
902 "log_event" => "consensus/cometbft/verifier/chain_context/failed",
903 "error" => ?e,
904 );
905 return Err(Error::ChainContextTransitionFailed(anyhow!(
906 "invalid genesis block"
907 )));
908 }
909 }
910
911 info!(self.logger, "Consensus chain context transition done");
912
913 let header = &untrusted_block.signed_header.header;
914 let trust_root = TrustRoot {
915 height: header.height.into(),
916 hash: header.hash().to_string(),
917 runtime_id: self.runtime_id,
918 chain_context: host_info.consensus_chain_context,
919 };
920
921 Ok(TrustedState {
922 trust_root,
923 trusted_blocks: vec![untrusted_block.into()],
924 })
925 }
926}