1use std::{convert::TryInto, str::FromStr, sync::Arc, time::Duration};
3
4use anyhow::anyhow;
5use crossbeam::channel;
6use rand::{rngs::OsRng, Rng};
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 rng = OsRng {};
284 let mut nonce = [0u8; NONCE_SIZE];
285 rng.fill(&mut nonce);
286
287 let io = Io::new(&self.protocol);
289 let stwp = io.fetch_freshness_proof(&nonce).map_err(|err| {
290 Error::FreshnessVerificationFailed(anyhow!("failed to fetch freshness proof: {}", err))
291 })?;
292
293 let tx = self.verify_transaction(cache, instance, &stwp.signed_tx, &stwp.proof)?;
295
296 if tx.method != METHOD_PROVE_FRESHNESS {
298 return Err(Error::FreshnessVerificationFailed(anyhow!(
299 "invalid method name"
300 )));
301 }
302
303 let tx_nonce: Nonce = cbor::from_value(tx.body).map_err(|err| {
304 Error::FreshnessVerificationFailed(anyhow!("failed to decode nonce: {}", err))
305 })?;
306 match nonce.cmp(&tx_nonce) {
307 std::cmp::Ordering::Equal => (),
308 _ => return Err(Error::FreshnessVerificationFailed(anyhow!("invalid nonce"))),
309 }
310
311 info!(self.logger, "State freshness successfully verified");
312
313 Ok(())
314 }
315
316 fn verify_transaction(
317 &self,
318 cache: &mut Cache,
319 instance: &mut Instance,
320 signed_tx: &SignedTransaction,
321 proof: &Proof,
322 ) -> Result<Transaction, Error> {
323 if !signed_tx.verify(&self.chain_context) {
325 return Err(Error::TransactionVerificationFailed(anyhow!(
326 "failed to verify the signature"
327 )));
328 }
329
330 let verified_block = self
332 .verify_to_target(proof.height, cache, instance)
333 .map_err(|err| {
334 Error::TransactionVerificationFailed(anyhow!("failed to fetch the block: {}", err))
335 })?;
336
337 let header = verified_block.signed_header.header;
338 if header.height.value() != proof.height {
339 return Err(Error::TransactionVerificationFailed(anyhow!(
340 "invalid block"
341 )));
342 }
343
344 let root_hash = header
345 .data_hash
346 .ok_or_else(|| Error::TransactionVerificationFailed(anyhow!("root hash not found")))?;
347 let root_hash = match root_hash {
348 TMHash::Sha256(hash) => hash,
349 TMHash::None => {
350 return Err(Error::TransactionVerificationFailed(anyhow!(
351 "root hash not found"
352 )));
353 }
354 };
355
356 let digest = Sha256::digest(cbor::to_vec(signed_tx.clone()));
358 let mut tx_hash = [0u8; HASH_SIZE];
359 tx_hash.copy_from_slice(&digest);
360
361 let merkle_proof: merkle::Proof = cbor::from_slice(&proof.raw_proof).map_err(|err| {
363 Error::TransactionVerificationFailed(anyhow!("failed to decode Merkle proof: {}", err))
364 })?;
365
366 merkle_proof.verify(root_hash, tx_hash).map_err(|err| {
367 Error::TransactionVerificationFailed(anyhow!("failed to verify Merkle proof: {}", err))
368 })?;
369
370 let tx: Transaction = cbor::from_slice(signed_tx.blob.as_slice()).map_err(|err| {
372 Error::TransactionVerificationFailed(anyhow!("failed to decode transaction: {}", err))
373 })?;
374
375 Ok(tx)
376 }
377
378 fn state_root_from_metadata(
380 &self,
381 cache: &mut Cache,
382 instance: &mut Instance,
383 height: u64,
384 ) -> Result<Root, Error> {
385 debug!(
386 self.logger,
387 "Fetching state root from block metadata transaction"
388 );
389
390 let io = Io::new(&self.protocol);
392 let stwp = io.fetch_block_metadata(height).map_err(|err| {
393 Error::StateRoot(anyhow!(
394 "failed to fetch block metadata transaction: {}",
395 err
396 ))
397 })?;
398
399 let tx = self.verify_transaction(cache, instance, &stwp.signed_tx, &stwp.proof)?;
401
402 if tx.method != METHOD_META {
403 return Err(Error::StateRoot(anyhow!("invalid method name")));
404 }
405
406 let meta: BlockMetadata = cbor::from_value(tx.body).map_err(|err| {
407 Error::StateRoot(anyhow!(
408 "failed to decode block metadata transaction: {}",
409 err
410 ))
411 })?;
412
413 Ok(Root {
414 namespace: Namespace::default(),
415 version: height,
416 root_type: RootType::State,
417 hash: meta.state_root,
418 })
419 }
420
421 fn verify(
422 &self,
423 cache: &mut Cache,
424 instance: &mut Instance,
425 consensus_block: LightBlock,
426 runtime_header: Header,
427 epoch: EpochTime,
428 ) -> Result<ConsensusState, Error> {
429 predicates::verify_namespace(self.runtime_id, &runtime_header)?;
431 predicates::verify_round_advance(cache, &runtime_header, &consensus_block, epoch)?;
432 predicates::verify_consensus_advance(cache, &consensus_block)?;
433
434 let height = consensus_block.height;
436 let consensus_block = self.verify_consensus_block(cache, instance, consensus_block)?;
437
438 predicates::verify_time(&runtime_header, &consensus_block)?;
440
441 let state = self.consensus_state_at(cache, instance, height)?;
443
444 if let Some((state_root, state_epoch)) =
446 cache.verified_state_roots.get(&runtime_header.round)
447 {
448 if state_root == &runtime_header.state_root
449 && state_epoch == &epoch
450 && epoch == cache.last_verified_epoch
451 {
452 cache.last_verified_height = height;
456 cache.last_verified_round = runtime_header.round;
457
458 return Ok(state);
459 }
460
461 }
463
464 let next_state = self.consensus_state_at(cache, instance, height + 1)?;
468
469 predicates::verify_state_root(&next_state, &runtime_header)?;
471 predicates::verify_epoch(&next_state, epoch)?;
472
473 if cache.last_verified_epoch != epoch {
476 let latest_state = self.latest_consensus_state(cache, instance)?;
477 self.verify_freshness_with_rak(&latest_state, cache)?;
478 }
479
480 cache
482 .verified_state_roots
483 .put(runtime_header.round, (runtime_header.state_root, epoch));
484
485 cache.last_verified_height = height;
487 cache.last_verified_round = runtime_header.round;
488 cache.last_verified_epoch = epoch;
489
490 Ok(state)
491 }
492
493 fn verify_for_query(
494 &self,
495 cache: &mut Cache,
496 instance: &mut Instance,
497 consensus_block: LightBlock,
498 runtime_header: Header,
499 epoch: EpochTime,
500 ) -> Result<ConsensusState, Error> {
501 predicates::verify_namespace(self.runtime_id, &runtime_header)?;
503
504 let height = consensus_block.height;
506 let consensus_block = self.verify_consensus_block(cache, instance, consensus_block)?;
507
508 predicates::verify_time(&runtime_header, &consensus_block)?;
510
511 let state = self.consensus_state_at(cache, instance, height)?;
513
514 if let Some((state_root, state_epoch)) =
516 cache.verified_state_roots.get(&runtime_header.round)
517 {
518 if state_root == &runtime_header.state_root && state_epoch == &epoch {
519 return Ok(state);
521 }
522
523 }
525
526 let next_state = self.consensus_state_at(cache, instance, height + 1)?;
530
531 predicates::verify_state_root(&next_state, &runtime_header)?;
533 predicates::verify_epoch(&next_state, epoch)?;
534
535 cache
537 .verified_state_roots
538 .put(runtime_header.round, (runtime_header.state_root, epoch));
539
540 Ok(state)
541 }
542
543 fn events_at(&self, height: u64, kind: EventKind) -> Result<Vec<Event>, Error> {
544 let result = self
545 .protocol
546 .call_host(Body::HostFetchConsensusEventsRequest(
547 HostFetchConsensusEventsRequest { height, kind },
548 ))
549 .map_err(|err| Error::VerificationFailed(err.into()))?;
550 match result {
553 Body::HostFetchConsensusEventsResponse(HostFetchConsensusEventsResponse { events }) => {
554 Ok(events)
555 }
556 _ => Err(Error::VerificationFailed(anyhow!("bad response from host"))),
557 }
558 }
559
560 fn update_insecure_posix_time(&self, verified_block: &TMLightBlock) {
561 time::update_insecure_posix_time(
565 verified_block
566 .signed_header
567 .header
568 .time
569 .duration_since(Time::unix_epoch())
570 .unwrap()
571 .as_secs()
572 .try_into()
573 .unwrap(),
574 );
575 }
576
577 pub fn start(self) {
579 std::thread::spawn(move || {
580 let _guard = self.tokio_runtime.enter(); let logger = get_logger("consensus/cometbft/verifier");
583 info!(logger, "Starting consensus verifier");
584
585 for retry in 1..=MAX_INITIALIZATION_RETRIES {
590 let result =
592 match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| self.run())) {
593 Ok(result) => result,
594 Err(_) => {
595 error!(logger, "Consensus verifier aborted");
596 process::abort();
597 }
598 };
599
600 match result {
602 Ok(_) => {}
603 Err(err @ Error::Builder(_))
604 | Err(err @ Error::TrustedStateLoadingFailed)
605 | Err(err @ Error::ChainContextTransitionFailed(_)) => {
606 error!(logger, "Consensus verifier failed to initialize, retrying";
607 "err" => %err,
608 "retry" => retry,
609 );
610 }
611 Err(err) => {
612 error!(logger, "Consensus verifier terminated, aborting";
614 "err" => %err,
615 );
616 process::abort();
617 }
618 }
619
620 std::thread::sleep(Duration::from_secs(1));
622 }
623
624 error!(logger, "Failed to start consensus verifier, aborting");
625 process::abort();
626 });
627 }
628
629 fn run(&self) -> Result<(), Error> {
630 let options = light_client::Options {
632 trust_threshold: Default::default(),
633 trusting_period: Duration::from_secs(3600 * 24 * 365 * 10), clock_drift: Duration::from_secs(60),
637 };
638
639 let peer_id = PeerId::new([0; 20]);
641 let clock = Box::new(InsecureClock);
642 let verifier = Box::new(PredicateVerifier::new(
643 ProdPredicates,
644 ProvidedVotingPowerCalculator::<signature::DomSepVerifier>::default(),
645 ProdCommitValidator,
646 ));
647 let io = Box::new(Io::new(&self.protocol));
648
649 info!(self.logger, "Loading trusted state");
652 let trusted_state = self
653 .trusted_state_store
654 .load(self.runtime_version, &self.trust_root);
655
656 let trusted_state: TrustedState = match trusted_state {
657 Ok(state) => state,
658 Err(err) => {
659 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)");
660 return Err(err);
661 }
662 };
663
664 info!(self.logger, "Checking chain context change");
667 let trusted_state = self.handle_chain_context_change(
668 trusted_state,
669 verifier.as_ref(),
670 clock.as_ref(),
671 io.as_ref(),
672 )?;
673
674 let mut store = Box::new(LruStore::new(
676 512,
677 trusted_state.trust_root.height.try_into().unwrap(),
678 ));
679 for lb in trusted_state.trusted_blocks {
680 store.insert(lb.into(), Status::Trusted);
681 }
682 let trust_root = trusted_state.trust_root;
683
684 let builder = LightClientBuilder::custom(
685 peer_id,
686 options,
687 store,
688 io,
689 clock,
690 verifier,
691 Box::new(components::scheduler::basic_bisecting_schedule),
692 Box::new(ProdPredicates),
693 );
694
695 let mut instance = builder
696 .trust_primary_at(
697 trust_root.height.try_into().unwrap(),
698 TMHash::from_str(&trust_root.hash.to_uppercase()).unwrap(),
699 )
700 .map_err(|err| Error::Builder(err.into()))?
701 .build();
702
703 info!(self.logger, "Consensus verifier initialized";
704 "trust_root_height" => trust_root.height,
705 "trust_root_hash" => ?trust_root.hash,
706 "trust_root_runtime_id" => ?trust_root.runtime_id,
707 "trust_root_chain_context" => ?trust_root.chain_context,
708 );
709
710 let host_node_id =
711 block_on(self.protocol.identity()).expect("host should provide a node identity");
712
713 let mut cache = Cache::new(host_node_id);
714
715 let verified_block = self.verify_to_target(HEIGHT_LATEST, &mut cache, &mut instance)?;
718
719 self.trusted_state_store
720 .save(self.runtime_version, &instance.state.light_store);
721
722 let mut last_saved_verified_block_height =
723 verified_block.signed_header.header.height.value();
724
725 info!(self.logger, "Consensus verifier synced";
726 "latest_height" => cache.latest_known_height(),
727 );
728
729 self.verify_freshness_with_proof(&mut instance, &mut cache)?;
733
734 loop {
736 let command = self.command_receiver.recv().map_err(|_| Error::Internal)?;
737
738 match command {
739 Command::Synchronize(height, sender) => {
740 sender
741 .send(self.sync(&mut cache, &mut instance, height))
742 .map_err(|_| Error::Internal)?;
743 }
744 Command::Verify(consensus_block, runtime_header, epoch, sender, false) => {
745 sender
746 .send(self.verify(
747 &mut cache,
748 &mut instance,
749 consensus_block,
750 runtime_header,
751 epoch,
752 ))
753 .map_err(|_| Error::Internal)?;
754 }
755 Command::Verify(consensus_block, runtime_header, epoch, sender, true) => {
756 sender
757 .send(self.verify_for_query(
758 &mut cache,
759 &mut instance,
760 consensus_block,
761 runtime_header,
762 epoch,
763 ))
764 .map_err(|_| Error::Internal)?;
765 }
766 Command::LatestState(sender) => {
767 sender
768 .send(self.latest_consensus_state(&mut cache, &mut instance))
769 .map_err(|_| Error::Internal)?;
770 }
771 Command::StateAt(height, sender) => {
772 sender
773 .send(self.consensus_state_at(&mut cache, &mut instance, height))
774 .map_err(|_| Error::Internal)?;
775 }
776 Command::LatestHeight(sender) => {
777 sender
778 .send(self.latest_consensus_height(&cache))
779 .map_err(|_| Error::Internal)?;
780 }
781 Command::EventsAt(height, kind, sender) => {
782 sender
783 .send(self.events_at(height, kind))
784 .map_err(|_| Error::Internal)?;
785 }
786 }
787
788 if let Some(last_verified_block) = cache.last_verified_block.as_ref() {
790 let last_height = last_verified_block.signed_header.header.height.into();
791 if last_height - last_saved_verified_block_height > TRUSTED_STATE_SAVE_INTERVAL {
792 self.trusted_state_store
793 .save(self.runtime_version, &instance.state.light_store);
794 last_saved_verified_block_height = last_height;
795 }
796 }
797 }
798 }
799
800 fn handle_chain_context_change(
801 &self,
802 mut trusted_state: TrustedState,
803 verifier: &impl TMVerifier,
804 clock: &impl components::clock::Clock,
805 io: &Io,
806 ) -> Result<TrustedState, Error> {
807 let host_info = self.protocol.get_host_info();
808
809 if trusted_state.trust_root.chain_context == host_info.consensus_chain_context {
811 info!(self.logger, "Consensus chain context hasn't changed");
812 return Ok(trusted_state);
813 }
814 info!(self.logger, "Consensus chain context has changed");
815
816 let trusted_block: TMLightBlock = trusted_state
820 .trusted_blocks
821 .pop()
822 .ok_or_else(|| {
823 Error::ChainContextTransitionFailed(anyhow!(
824 "cannot transition from embedded trust root"
825 ))
826 })?
827 .into();
828
829 let height = io
833 .fetch_genesis_height()
834 .map_err(|err| Error::ChainContextTransitionFailed(err.into()))?;
835 let height = AtHeight::At(height.try_into().unwrap());
836 let untrusted_block = components::io::Io::fetch_light_block(io, height)
837 .map_err(|err| Error::ChainContextTransitionFailed(err.into()))?;
838
839 if untrusted_block.signed_header.header.last_block_id.is_some() {
840 return Err(Error::ChainContextTransitionFailed(anyhow!(
841 "invalid genesis block"
842 )));
843 }
844
845 let untrusted = untrusted_block.as_untrusted_state();
846
847 let header = trusted_block.signed_header.header;
855 let height = header.height;
856 let height = if height.increment() != untrusted.height() {
857 height
858 } else {
859 height
860 .value()
861 .checked_sub(1)
862 .ok_or_else(|| Error::ChainContextTransitionFailed(anyhow!("height underflow")))?
863 .try_into()
864 .unwrap()
865 };
866
867 let trusted = TrustedBlockState {
868 header_time: header.time,
869 height,
870 next_validators: &trusted_block.validators,
871 next_validators_hash: header.validators_hash,
872 chain_id: &chain_id(&host_info.consensus_chain_context),
874 };
875
876 let options = light_client::Options {
878 trust_threshold: TrustThreshold::TWO_THIRDS,
879 trusting_period: Duration::from_secs(3600 * 24 * 365 * 10), clock_drift: Duration::from_secs(60),
881 };
882 let now = clock.now();
883
884 let verdict = verifier.verify_update_header(untrusted, trusted, &options, now);
885
886 match verdict {
887 Verdict::Success => (),
888 Verdict::NotEnoughTrust(tally) => {
889 info!(
890 self.logger,
891 "Not enough trust to accept new chain context";
892 "log_event" => "consensus/cometbft/verifier/chain_context/no_trust",
893 "tally" => ?tally,
894 );
895 return Err(Error::ChainContextTransitionFailed(anyhow!(
896 "not enough trust"
897 )));
898 }
899 Verdict::Invalid(e) => {
900 info!(
901 self.logger,
902 "Failed to accept new chain context";
903 "log_event" => "consensus/cometbft/verifier/chain_context/failed",
904 "error" => ?e,
905 );
906 return Err(Error::ChainContextTransitionFailed(anyhow!(
907 "invalid genesis block"
908 )));
909 }
910 }
911
912 info!(self.logger, "Consensus chain context transition done");
913
914 let header = &untrusted_block.signed_header.header;
915 let trust_root = TrustRoot {
916 height: header.height.into(),
917 hash: header.hash().to_string(),
918 runtime_id: self.runtime_id,
919 chain_context: host_info.consensus_chain_context,
920 };
921
922 Ok(TrustedState {
923 trust_root,
924 trusted_blocks: vec![untrusted_block.into()],
925 })
926 }
927}