oasis_core_runtime/consensus/tendermint/verifier/
mod.rs

1//! Tendermint consensus layer verification logic.
2use 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
58// Modules.
59mod cache;
60mod clock;
61mod handle;
62mod io;
63mod noop;
64mod predicates;
65mod signature;
66mod store;
67mod types;
68
69// Re-exports.
70pub use noop::NopVerifier;
71
72/// Maximum number of times to retry initialization.
73const MAX_INITIALIZATION_RETRIES: usize = 3;
74
75/// Trusted state save interval (in consensus blocks).
76const TRUSTED_STATE_SAVE_INTERVAL: u64 = 128;
77
78/// Tendermint consensus layer verifier.
79pub 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    /// Create a new Tendermint consensus layer verifier.
94    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    /// Return a handle to interact with the verifier.
127    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        // Clear verification trace as it could otherwise lead to infinite memory growth.
149        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            // Ignore requests for earlier heights.
163            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        // When latest state is requested we always perform same-block execution verification.
175        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        // Obtain an authoritative state root, either from the current block if it is already
197        // finalized or from the metadata transaction of the previous block.
198        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        // Decode passed block as a Tendermint block.
217        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        // Verify up to the block at current height.
226        // Only does forward verification and fails if height is lower than the last trust height.
227        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        // Validate passed consensus block.
236        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    /// Verify state freshness using RAK and nonces.
244    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    /// Verify state freshness using prove freshness transaction.
265    ///
266    /// Verification is done in three steps. In the first one, the verifier selects a unique nonce
267    /// and sends it to the host. The second step is done by the host, who prepares, signs and
268    /// submits a prove freshness transaction using the received nonce. Once transaction is included
269    /// in a block, the host replies with block's height, transaction details and a Merkle proof
270    /// that the transaction was included in the block. In the final step, the verifier verifies
271    /// the proof and accepts state as fresh iff verification succeeds.
272    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        // Generate a random nonce for prove freshness transaction.
283        let mut rng = OsRng {};
284        let mut nonce = [0u8; NONCE_SIZE];
285        rng.fill(&mut nonce);
286
287        // Ask host for freshness proof.
288        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        // Verify the transaction and the proof.
294        let tx = self.verify_transaction(cache, instance, &stwp.signed_tx, &stwp.proof)?;
295
296        // Verify the method name and the nonce.
297        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        // Verify the signature.
324        if !signed_tx.verify(&self.chain_context) {
325            return Err(Error::TransactionVerificationFailed(anyhow!(
326                "failed to verify the signature"
327            )));
328        }
329
330        // Fetch the root hash of a block in which the transaction was published.
331        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        // Compute hash of the transaction.
357        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        // Decode raw proof as a CometBFT Merkle proof of inclusion.
362        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        // Decode transaction.
371        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    /// Fetch state root from block metadata transaction.
379    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        // Ask the host for block metadata transaction.
391        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        // Verify the transaction and the proof.
400        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        // Perform basic verifications.
430        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        // Verify the consensus layer block.
435        let height = consensus_block.height;
436        let consensus_block = self.verify_consensus_block(cache, instance, consensus_block)?;
437
438        // Perform basic verifications.
439        predicates::verify_time(&runtime_header, &consensus_block)?;
440
441        // Obtain an authoritative state root.
442        let state = self.consensus_state_at(cache, instance, height)?;
443
444        // Check if we have already verified this runtime header to avoid re-verification.
445        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                // Header and epoch matches, no need to perform re-verification.
453
454                // Cache last verified fields.
455                cache.last_verified_height = height;
456                cache.last_verified_round = runtime_header.round;
457
458                return Ok(state);
459            }
460
461            // Force full verification in case of cache mismatch.
462        }
463
464        // Obtain an authoritative state root for full verification.
465        // Note that we cannot return the state at height+1 as the block might not have been
466        // finalized yet, and we won't be able to query block results such as events.
467        let next_state = self.consensus_state_at(cache, instance, height + 1)?;
468
469        // Perform full verification.
470        predicates::verify_state_root(&next_state, &runtime_header)?;
471        predicates::verify_epoch(&next_state, epoch)?;
472
473        // Verify our own RAK is published in registry once per epoch.
474        // This ensures consensus state is recent enough.
475        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 verified state root and epoch.
481        cache
482            .verified_state_roots
483            .put(runtime_header.round, (runtime_header.state_root, epoch));
484
485        // Cache last verified fields.
486        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        // Perform basic verifications.
502        predicates::verify_namespace(self.runtime_id, &runtime_header)?;
503
504        // Verify the consensus layer block.
505        let height = consensus_block.height;
506        let consensus_block = self.verify_consensus_block(cache, instance, consensus_block)?;
507
508        // Perform basic verifications.
509        predicates::verify_time(&runtime_header, &consensus_block)?;
510
511        // Obtain an authoritative state root.
512        let state = self.consensus_state_at(cache, instance, height)?;
513
514        // Check if we have already verified this runtime header to avoid re-verification.
515        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                // Header and epoch matches, no need to perform re-verification.
520                return Ok(state);
521            }
522
523            // Force full verification in case of cache mismatch.
524        }
525
526        // Obtain an authoritative state root for full verification.
527        // Note that we cannot return the state at height+1 as the block might not have been
528        // finalized yet, and we won't be able to query block results such as events.
529        let next_state = self.consensus_state_at(cache, instance, height + 1)?;
530
531        // Perform full verification.
532        predicates::verify_state_root(&next_state, &runtime_header)?;
533        predicates::verify_epoch(&next_state, epoch)?;
534
535        // Cache verified state root and epoch.
536        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        // TODO: Perform event verification once this becomes possible.
551
552        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        // Update untrusted time if ahead. This makes sure that the enclave's sense of time is
562        // synced with consensus sense of time based on the fact that consensus time is harder to
563        // fake than host operating system time.
564        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    /// Start the verifier in a separate thread.
578    pub fn start(self) {
579        std::thread::spawn(move || {
580            let _guard = self.tokio_runtime.enter(); // Ensure Tokio runtime is available.
581
582            let logger = get_logger("consensus/cometbft/verifier");
583            info!(logger, "Starting consensus verifier");
584
585            // Try to initialize a couple of times as initially it may be the case that we have
586            // started while the Runtime Host Protocol has not been fully initialized so the host
587            // is still rejecting requests. This is the case because `start()` is called as part
588            // of the RHP initialization itself (when handling a `RuntimeInfoRequest`).
589            for retry in 1..=MAX_INITIALIZATION_RETRIES {
590                // Handle panics by logging and aborting the runtime.
591                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                // Handle failures.
601                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                        // All other errors are fatal.
613                        error!(logger, "Consensus verifier terminated, aborting";
614                            "err" => %err,
615                        );
616                        process::abort();
617                    }
618                }
619
620                // Retry to initialize the verifier.
621                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        // Create a new light client instance.
631        let options = light_client::Options {
632            trust_threshold: Default::default(),
633            // XXX: Until we have a way to retrieve trusted light client headers from other nodes
634            //      (e.g., via EnclaveRPC) there is little sense in specifying a trusting period.
635            trusting_period: Duration::from_secs(3600 * 24 * 365 * 10), // 10 years
636            clock_drift: Duration::from_secs(60),
637        };
638
639        // NOTE: Peer identifier is irrelevant as the enclave is totally eclipsed.
640        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        // Build a light client using the embedded trust root or trust root
650        // stored in the local store.
651        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        // Verify if we can trust light blocks from a new chain if the consensus
665        // chain context changes.
666        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        // Insert all of the trusted blocks into the light store as trusted.
675        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        // Sync the verifier up to the latest block to make sure we are up to date before
716        // processing any requests.
717        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        // Verify state freshness with freshness proof. This step is required only for clients
730        // as executors and key managers verify freshness regularly using node registration
731        // (RAK with random nonces).
732        self.verify_freshness_with_proof(&mut instance, &mut cache)?;
733
734        // Start the command processing loop.
735        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            // Persist last verified block once in a while.
789            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        // Nothing to handle.
810        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        // Chain context transition cannot be done directly from the embedded
817        // trust root as we don't have access to the matching trusted light
818        // block which validator set we need to verify blocks from the new chain.
819        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        // Fetch genesis block from the host and prepare untrusted state for
830        // verification. Since host cannot be trusted we need to verify if
831        // fetched height and block belong to the genesis.
832        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        // Prepare trusted state for verification. As we are using the verifier
848        // to verify the untrusted block and state transition, we must make
849        // sure that trusted and untrusted states don't belong to consecutive
850        // blocks as otherwise validator set hash will get verified also.
851        // Keeping heights at minimum distance of 2 will make sure that the
852        // verifier will check if there is enough overlap between the validator
853        // sets.
854        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            // We need to use the target chain ID as we know it has changed.
873            chain_id: &chain_id(&host_info.consensus_chain_context),
874        };
875
876        // Verify the new block using +2/3 trust threshold rule.
877        let options = light_client::Options {
878            trust_threshold: TrustThreshold::TWO_THIRDS,
879            trusting_period: Duration::from_secs(3600 * 24 * 365 * 10), // 10 years
880            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}