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 < cache.last_verified_height || height < cache.latest_known_height().unwrap_or(0)
159        {
160            // Ignore requests for earlier heights.
161            return Ok(());
162        }
163        self.verify_to_target(height, cache, instance)?;
164        Ok(())
165    }
166
167    fn latest_consensus_state(
168        &self,
169        cache: &mut Cache,
170        instance: &mut Instance,
171    ) -> Result<ConsensusState, Error> {
172        // When latest state is requested we always perform same-block execution verification.
173        let height = self.latest_consensus_height(cache)?;
174        let state_root = self.state_root_from_metadata(cache, instance, height)?;
175
176        Ok(ConsensusState::from_protocol(
177            self.protocol.clone(),
178            state_root.version,
179            state_root,
180        ))
181    }
182
183    fn latest_consensus_height(&self, cache: &Cache) -> Result<u64, Error> {
184        let height = cache.latest_known_height().ok_or(Error::Internal)?;
185        Ok(height)
186    }
187
188    fn consensus_state_at(
189        &self,
190        cache: &mut Cache,
191        instance: &mut Instance,
192        height: u64,
193    ) -> Result<ConsensusState, Error> {
194        // Obtain an authoritative state root, either from the current block if it is already
195        // finalized or from the metadata transaction of the previous block.
196        let state_root = match self.verify_to_target(height, cache, instance) {
197            Ok(verified_block) => state_root_from_header(&verified_block.signed_header),
198            Err(_) => self.state_root_from_metadata(cache, instance, height - 1)?,
199        };
200
201        Ok(ConsensusState::from_protocol(
202            self.protocol.clone(),
203            state_root.version + 1,
204            state_root,
205        ))
206    }
207
208    fn verify_consensus_block(
209        &self,
210        cache: &mut Cache,
211        instance: &mut Instance,
212        consensus_block: LightBlock,
213    ) -> Result<LightBlockMeta, Error> {
214        // Decode passed block as a Tendermint block.
215        let lb_height = consensus_block.height;
216        let untrusted_block =
217            decode_light_block(consensus_block).map_err(Error::VerificationFailed)?;
218        let untrusted_header = untrusted_block
219            .signed_header
220            .as_ref()
221            .ok_or_else(|| Error::VerificationFailed(anyhow!("missing signed header")))?;
222
223        // Verify up to the block at current height.
224        // Only does forward verification and fails if height is lower than the last trust height.
225        let height = untrusted_header.header().height.value();
226        if height != lb_height {
227            return Err(Error::VerificationFailed(anyhow!(
228                "inconsistent light block/header height"
229            )));
230        }
231        let verified_block = self.verify_to_target(height, cache, instance)?;
232
233        // Validate passed consensus block.
234        if untrusted_header.header() != verified_block.signed_header.header() {
235            return Err(Error::VerificationFailed(anyhow!("header mismatch")));
236        }
237
238        Ok(untrusted_block)
239    }
240
241    /// Verify state freshness using RAK and nonces.
242    fn verify_freshness_with_rak(
243        &self,
244        state: &ConsensusState,
245        cache: &Cache,
246    ) -> Result<(), Error> {
247        let identity = if let Some(identity) = self.protocol.get_identity() {
248            identity
249        } else {
250            return Ok(());
251        };
252
253        verify_state_freshness(
254            state,
255            identity,
256            &self.runtime_id,
257            &self.runtime_version,
258            &cache.host_node_id,
259        )
260    }
261
262    /// Verify state freshness using prove freshness transaction.
263    ///
264    /// Verification is done in three steps. In the first one, the verifier selects a unique nonce
265    /// and sends it to the host. The second step is done by the host, who prepares, signs and
266    /// submits a prove freshness transaction using the received nonce. Once transaction is included
267    /// in a block, the host replies with block's height, transaction details and a Merkle proof
268    /// that the transaction was included in the block. In the final step, the verifier verifies
269    /// the proof and accepts state as fresh iff verification succeeds.
270    fn verify_freshness_with_proof(
271        &self,
272        instance: &mut Instance,
273        cache: &mut Cache,
274    ) -> Result<(), Error> {
275        info!(
276            self.logger,
277            "Verifying state freshness using prove freshness transaction"
278        );
279
280        // Generate a random nonce for prove freshness transaction.
281        let mut rng = OsRng {};
282        let mut nonce = [0u8; NONCE_SIZE];
283        rng.fill(&mut nonce);
284
285        // Ask host for freshness proof.
286        let io = Io::new(&self.protocol);
287        let stwp = io.fetch_freshness_proof(&nonce).map_err(|err| {
288            Error::FreshnessVerificationFailed(anyhow!("failed to fetch freshness proof: {}", err))
289        })?;
290
291        // Verify the transaction and the proof.
292        let tx = self.verify_transaction(cache, instance, &stwp.signed_tx, &stwp.proof)?;
293
294        // Verify the method name and the nonce.
295        if tx.method != METHOD_PROVE_FRESHNESS {
296            return Err(Error::FreshnessVerificationFailed(anyhow!(
297                "invalid method name"
298            )));
299        }
300
301        let tx_nonce: Nonce = cbor::from_value(tx.body).map_err(|err| {
302            Error::FreshnessVerificationFailed(anyhow!("failed to decode nonce: {}", err))
303        })?;
304        match nonce.cmp(&tx_nonce) {
305            std::cmp::Ordering::Equal => (),
306            _ => return Err(Error::FreshnessVerificationFailed(anyhow!("invalid nonce"))),
307        }
308
309        info!(self.logger, "State freshness successfully verified");
310
311        Ok(())
312    }
313
314    fn verify_transaction(
315        &self,
316        cache: &mut Cache,
317        instance: &mut Instance,
318        signed_tx: &SignedTransaction,
319        proof: &Proof,
320    ) -> Result<Transaction, Error> {
321        // Verify the signature.
322        if !signed_tx.verify(&self.chain_context) {
323            return Err(Error::TransactionVerificationFailed(anyhow!(
324                "failed to verify the signature"
325            )));
326        }
327
328        // Fetch the root hash of a block in which the transaction was published.
329        let verified_block = self
330            .verify_to_target(proof.height, cache, instance)
331            .map_err(|err| {
332                Error::TransactionVerificationFailed(anyhow!("failed to fetch the block: {}", err))
333            })?;
334
335        let header = verified_block.signed_header.header;
336        if header.height.value() != proof.height {
337            return Err(Error::TransactionVerificationFailed(anyhow!(
338                "invalid block"
339            )));
340        }
341
342        let root_hash = header
343            .data_hash
344            .ok_or_else(|| Error::TransactionVerificationFailed(anyhow!("root hash not found")))?;
345        let root_hash = match root_hash {
346            TMHash::Sha256(hash) => hash,
347            TMHash::None => {
348                return Err(Error::TransactionVerificationFailed(anyhow!(
349                    "root hash not found"
350                )));
351            }
352        };
353
354        // Compute hash of the transaction.
355        let digest = Sha256::digest(cbor::to_vec(signed_tx.clone()));
356        let mut tx_hash = [0u8; HASH_SIZE];
357        tx_hash.copy_from_slice(&digest);
358
359        // Decode raw proof as a CometBFT Merkle proof of inclusion.
360        let merkle_proof: merkle::Proof = cbor::from_slice(&proof.raw_proof).map_err(|err| {
361            Error::TransactionVerificationFailed(anyhow!("failed to decode Merkle proof: {}", err))
362        })?;
363
364        merkle_proof.verify(root_hash, tx_hash).map_err(|err| {
365            Error::TransactionVerificationFailed(anyhow!("failed to verify Merkle proof: {}", err))
366        })?;
367
368        // Decode transaction.
369        let tx: Transaction = cbor::from_slice(signed_tx.blob.as_slice()).map_err(|err| {
370            Error::TransactionVerificationFailed(anyhow!("failed to decode transaction: {}", err))
371        })?;
372
373        Ok(tx)
374    }
375
376    /// Fetch state root from block metadata transaction.
377    fn state_root_from_metadata(
378        &self,
379        cache: &mut Cache,
380        instance: &mut Instance,
381        height: u64,
382    ) -> Result<Root, Error> {
383        debug!(
384            self.logger,
385            "Fetching state root from block metadata transaction"
386        );
387
388        // Ask the host for block metadata transaction.
389        let io = Io::new(&self.protocol);
390        let stwp = io.fetch_block_metadata(height).map_err(|err| {
391            Error::StateRoot(anyhow!(
392                "failed to fetch block metadata transaction: {}",
393                err
394            ))
395        })?;
396
397        // Verify the transaction and the proof.
398        let tx = self.verify_transaction(cache, instance, &stwp.signed_tx, &stwp.proof)?;
399
400        if tx.method != METHOD_META {
401            return Err(Error::StateRoot(anyhow!("invalid method name")));
402        }
403
404        let meta: BlockMetadata = cbor::from_value(tx.body).map_err(|err| {
405            Error::StateRoot(anyhow!(
406                "failed to decode block metadata transaction: {}",
407                err
408            ))
409        })?;
410
411        Ok(Root {
412            namespace: Namespace::default(),
413            version: height,
414            root_type: RootType::State,
415            hash: meta.state_root,
416        })
417    }
418
419    fn verify(
420        &self,
421        cache: &mut Cache,
422        instance: &mut Instance,
423        consensus_block: LightBlock,
424        runtime_header: Header,
425        epoch: EpochTime,
426    ) -> Result<ConsensusState, Error> {
427        // Perform basic verifications.
428        predicates::verify_namespace(self.runtime_id, &runtime_header)?;
429        predicates::verify_round_advance(cache, &runtime_header, &consensus_block, epoch)?;
430        predicates::verify_consensus_advance(cache, &consensus_block)?;
431
432        // Verify the consensus layer block.
433        let height = consensus_block.height;
434        let consensus_block = self.verify_consensus_block(cache, instance, consensus_block)?;
435
436        // Perform basic verifications.
437        predicates::verify_time(&runtime_header, &consensus_block)?;
438
439        // Obtain an authoritative state root.
440        let state = self.consensus_state_at(cache, instance, height)?;
441
442        // Check if we have already verified this runtime header to avoid re-verification.
443        if let Some((state_root, state_epoch)) =
444            cache.verified_state_roots.get(&runtime_header.round)
445        {
446            if state_root == &runtime_header.state_root
447                && state_epoch == &epoch
448                && epoch == cache.last_verified_epoch
449            {
450                // Header and epoch matches, no need to perform re-verification.
451
452                // Cache last verified fields.
453                cache.last_verified_height = height;
454                cache.last_verified_round = runtime_header.round;
455
456                return Ok(state);
457            }
458
459            // Force full verification in case of cache mismatch.
460        }
461
462        // Obtain an authoritative state root for full verification.
463        // Note that we cannot return the state at height+1 as the block might not have been
464        // finalized yet, and we won't be able to query block results such as events.
465        let next_state = self.consensus_state_at(cache, instance, height + 1)?;
466
467        // Perform full verification.
468        predicates::verify_state_root(&next_state, &runtime_header)?;
469        predicates::verify_epoch(&next_state, epoch)?;
470
471        // Verify our own RAK is published in registry once per epoch.
472        // This ensures consensus state is recent enough.
473        if cache.last_verified_epoch != epoch {
474            let latest_state = self.latest_consensus_state(cache, instance)?;
475            self.verify_freshness_with_rak(&latest_state, cache)?;
476        }
477
478        // Cache verified state root and epoch.
479        cache
480            .verified_state_roots
481            .put(runtime_header.round, (runtime_header.state_root, epoch));
482
483        // Cache last verified fields.
484        cache.last_verified_height = height;
485        cache.last_verified_round = runtime_header.round;
486        cache.last_verified_epoch = epoch;
487
488        Ok(state)
489    }
490
491    fn verify_for_query(
492        &self,
493        cache: &mut Cache,
494        instance: &mut Instance,
495        consensus_block: LightBlock,
496        runtime_header: Header,
497        epoch: EpochTime,
498    ) -> Result<ConsensusState, Error> {
499        // Perform basic verifications.
500        predicates::verify_namespace(self.runtime_id, &runtime_header)?;
501
502        // Verify the consensus layer block.
503        let height = consensus_block.height;
504        let consensus_block = self.verify_consensus_block(cache, instance, consensus_block)?;
505
506        // Perform basic verifications.
507        predicates::verify_time(&runtime_header, &consensus_block)?;
508
509        // Obtain an authoritative state root.
510        let state = self.consensus_state_at(cache, instance, height)?;
511
512        // Check if we have already verified this runtime header to avoid re-verification.
513        if let Some((state_root, state_epoch)) =
514            cache.verified_state_roots.get(&runtime_header.round)
515        {
516            if state_root == &runtime_header.state_root && state_epoch == &epoch {
517                // Header and epoch matches, no need to perform re-verification.
518                return Ok(state);
519            }
520
521            // Force full verification in case of cache mismatch.
522        }
523
524        // Obtain an authoritative state root for full verification.
525        // Note that we cannot return the state at height+1 as the block might not have been
526        // finalized yet, and we won't be able to query block results such as events.
527        let next_state = self.consensus_state_at(cache, instance, height + 1)?;
528
529        // Perform full verification.
530        predicates::verify_state_root(&next_state, &runtime_header)?;
531        predicates::verify_epoch(&next_state, epoch)?;
532
533        // Cache verified state root and epoch.
534        cache
535            .verified_state_roots
536            .put(runtime_header.round, (runtime_header.state_root, epoch));
537
538        Ok(state)
539    }
540
541    fn events_at(&self, height: u64, kind: EventKind) -> Result<Vec<Event>, Error> {
542        let result = self
543            .protocol
544            .call_host(Body::HostFetchConsensusEventsRequest(
545                HostFetchConsensusEventsRequest { height, kind },
546            ))
547            .map_err(|err| Error::VerificationFailed(err.into()))?;
548        // TODO: Perform event verification once this becomes possible.
549
550        match result {
551            Body::HostFetchConsensusEventsResponse(HostFetchConsensusEventsResponse { events }) => {
552                Ok(events)
553            }
554            _ => Err(Error::VerificationFailed(anyhow!("bad response from host"))),
555        }
556    }
557
558    fn update_insecure_posix_time(&self, verified_block: &TMLightBlock) {
559        // Update untrusted time if ahead. This makes sure that the enclave's sense of time is
560        // synced with consensus sense of time based on the fact that consensus time is harder to
561        // fake than host operating system time.
562        time::update_insecure_posix_time(
563            verified_block
564                .signed_header
565                .header
566                .time
567                .duration_since(Time::unix_epoch())
568                .unwrap()
569                .as_secs()
570                .try_into()
571                .unwrap(),
572        );
573    }
574
575    /// Start the verifier in a separate thread.
576    pub fn start(self) {
577        std::thread::spawn(move || {
578            let _guard = self.tokio_runtime.enter(); // Ensure Tokio runtime is available.
579
580            let logger = get_logger("consensus/cometbft/verifier");
581            info!(logger, "Starting consensus verifier");
582
583            // Try to initialize a couple of times as initially it may be the case that we have
584            // started while the Runtime Host Protocol has not been fully initialized so the host
585            // is still rejecting requests. This is the case because `start()` is called as part
586            // of the RHP initialization itself (when handling a `RuntimeInfoRequest`).
587            for retry in 1..=MAX_INITIALIZATION_RETRIES {
588                // Handle panics by logging and aborting the runtime.
589                let result =
590                    match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| self.run())) {
591                        Ok(result) => result,
592                        Err(_) => {
593                            error!(logger, "Consensus verifier aborted");
594                            process::abort();
595                        }
596                    };
597
598                // Handle failures.
599                match result {
600                    Ok(_) => {}
601                    Err(err @ Error::Builder(_))
602                    | Err(err @ Error::TrustedStateLoadingFailed)
603                    | Err(err @ Error::ChainContextTransitionFailed(_)) => {
604                        error!(logger, "Consensus verifier failed to initialize, retrying";
605                            "err" => %err,
606                            "retry" => retry,
607                        );
608                    }
609                    Err(err) => {
610                        // All other errors are fatal.
611                        error!(logger, "Consensus verifier terminated, aborting";
612                            "err" => %err,
613                        );
614                        process::abort();
615                    }
616                }
617
618                // Retry to initialize the verifier.
619                std::thread::sleep(Duration::from_secs(1));
620            }
621
622            error!(logger, "Failed to start consensus verifier, aborting");
623            process::abort();
624        });
625    }
626
627    fn run(&self) -> Result<(), Error> {
628        // Create a new light client instance.
629        let options = light_client::Options {
630            trust_threshold: Default::default(),
631            // XXX: Until we have a way to retrieve trusted light client headers from other nodes
632            //      (e.g., via EnclaveRPC) there is little sense in specifying a trusting period.
633            trusting_period: Duration::from_secs(3600 * 24 * 365 * 10), // 10 years
634            clock_drift: Duration::from_secs(60),
635        };
636
637        // NOTE: Peer identifier is irrelevant as the enclave is totally eclipsed.
638        let peer_id = PeerId::new([0; 20]);
639        let clock = Box::new(InsecureClock);
640        let verifier = Box::new(PredicateVerifier::new(
641            ProdPredicates,
642            ProvidedVotingPowerCalculator::<signature::DomSepVerifier>::default(),
643            ProdCommitValidator,
644        ));
645        let io = Box::new(Io::new(&self.protocol));
646
647        // Build a light client using the embedded trust root or trust root
648        // stored in the local store.
649        info!(self.logger, "Loading trusted state");
650        let trusted_state = self
651            .trusted_state_store
652            .load(self.runtime_version, &self.trust_root);
653
654        let trusted_state: TrustedState = match trusted_state {
655            Ok(state) => state,
656            Err(err) => {
657                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)");
658                return Err(err);
659            }
660        };
661
662        // Verify if we can trust light blocks from a new chain if the consensus
663        // chain context changes.
664        info!(self.logger, "Checking chain context change");
665        let trusted_state = self.handle_chain_context_change(
666            trusted_state,
667            verifier.as_ref(),
668            clock.as_ref(),
669            io.as_ref(),
670        )?;
671
672        // Insert all of the trusted blocks into the light store as trusted.
673        let mut store = Box::new(LruStore::new(
674            512,
675            trusted_state.trust_root.height.try_into().unwrap(),
676        ));
677        for lb in trusted_state.trusted_blocks {
678            store.insert(lb.into(), Status::Trusted);
679        }
680        let trust_root = trusted_state.trust_root;
681
682        let builder = LightClientBuilder::custom(
683            peer_id,
684            options,
685            store,
686            io,
687            clock,
688            verifier,
689            Box::new(components::scheduler::basic_bisecting_schedule),
690            Box::new(ProdPredicates),
691        );
692
693        let mut instance = builder
694            .trust_primary_at(
695                trust_root.height.try_into().unwrap(),
696                TMHash::from_str(&trust_root.hash.to_uppercase()).unwrap(),
697            )
698            .map_err(|err| Error::Builder(err.into()))?
699            .build();
700
701        info!(self.logger, "Consensus verifier initialized";
702            "trust_root_height" => trust_root.height,
703            "trust_root_hash" => ?trust_root.hash,
704            "trust_root_runtime_id" => ?trust_root.runtime_id,
705            "trust_root_chain_context" => ?trust_root.chain_context,
706        );
707
708        let host_node_id =
709            block_on(self.protocol.identity()).expect("host should provide a node identity");
710
711        let mut cache = Cache::new(host_node_id);
712
713        // Sync the verifier up to the latest block to make sure we are up to date before
714        // processing any requests.
715        let verified_block = self.verify_to_target(HEIGHT_LATEST, &mut cache, &mut instance)?;
716
717        self.trusted_state_store
718            .save(self.runtime_version, &instance.state.light_store);
719
720        let mut last_saved_verified_block_height =
721            verified_block.signed_header.header.height.value();
722
723        info!(self.logger, "Consensus verifier synced";
724            "latest_height" => cache.latest_known_height(),
725        );
726
727        // Verify state freshness with freshness proof. This step is required only for clients
728        // as executors and key managers verify freshness regularly using node registration
729        // (RAK with random nonces).
730        self.verify_freshness_with_proof(&mut instance, &mut cache)?;
731
732        // Start the command processing loop.
733        loop {
734            let command = self.command_receiver.recv().map_err(|_| Error::Internal)?;
735
736            match command {
737                Command::Synchronize(height, sender) => {
738                    sender
739                        .send(self.sync(&mut cache, &mut instance, height))
740                        .map_err(|_| Error::Internal)?;
741                }
742                Command::Verify(consensus_block, runtime_header, epoch, sender, false) => {
743                    sender
744                        .send(self.verify(
745                            &mut cache,
746                            &mut instance,
747                            consensus_block,
748                            runtime_header,
749                            epoch,
750                        ))
751                        .map_err(|_| Error::Internal)?;
752                }
753                Command::Verify(consensus_block, runtime_header, epoch, sender, true) => {
754                    sender
755                        .send(self.verify_for_query(
756                            &mut cache,
757                            &mut instance,
758                            consensus_block,
759                            runtime_header,
760                            epoch,
761                        ))
762                        .map_err(|_| Error::Internal)?;
763                }
764                Command::LatestState(sender) => {
765                    sender
766                        .send(self.latest_consensus_state(&mut cache, &mut instance))
767                        .map_err(|_| Error::Internal)?;
768                }
769                Command::StateAt(height, sender) => {
770                    sender
771                        .send(self.consensus_state_at(&mut cache, &mut instance, height))
772                        .map_err(|_| Error::Internal)?;
773                }
774                Command::LatestHeight(sender) => {
775                    sender
776                        .send(self.latest_consensus_height(&cache))
777                        .map_err(|_| Error::Internal)?;
778                }
779                Command::EventsAt(height, kind, sender) => {
780                    sender
781                        .send(self.events_at(height, kind))
782                        .map_err(|_| Error::Internal)?;
783                }
784            }
785
786            // Persist last verified block once in a while.
787            if let Some(last_verified_block) = cache.last_verified_block.as_ref() {
788                let last_height = last_verified_block.signed_header.header.height.into();
789                if last_height - last_saved_verified_block_height > TRUSTED_STATE_SAVE_INTERVAL {
790                    self.trusted_state_store
791                        .save(self.runtime_version, &instance.state.light_store);
792                    last_saved_verified_block_height = last_height;
793                }
794            }
795        }
796    }
797
798    fn handle_chain_context_change(
799        &self,
800        mut trusted_state: TrustedState,
801        verifier: &impl TMVerifier,
802        clock: &impl components::clock::Clock,
803        io: &Io,
804    ) -> Result<TrustedState, Error> {
805        let host_info = self.protocol.get_host_info();
806
807        // Nothing to handle.
808        if trusted_state.trust_root.chain_context == host_info.consensus_chain_context {
809            info!(self.logger, "Consensus chain context hasn't changed");
810            return Ok(trusted_state);
811        }
812        info!(self.logger, "Consensus chain context has changed");
813
814        // Chain context transition cannot be done directly from the embedded
815        // trust root as we don't have access to the matching trusted light
816        // block which validator set we need to verify blocks from the new chain.
817        let trusted_block: TMLightBlock = trusted_state
818            .trusted_blocks
819            .pop()
820            .ok_or_else(|| {
821                Error::ChainContextTransitionFailed(anyhow!(
822                    "cannot transition from embedded trust root"
823                ))
824            })?
825            .into();
826
827        // Fetch genesis block from the host and prepare untrusted state for
828        // verification. Since host cannot be trusted we need to verify if
829        // fetched height and block belong to the genesis.
830        let height = io
831            .fetch_genesis_height()
832            .map_err(|err| Error::ChainContextTransitionFailed(err.into()))?;
833        let height = AtHeight::At(height.try_into().unwrap());
834        let untrusted_block = components::io::Io::fetch_light_block(io, height)
835            .map_err(|err| Error::ChainContextTransitionFailed(err.into()))?;
836
837        if untrusted_block.signed_header.header.last_block_id.is_some() {
838            return Err(Error::ChainContextTransitionFailed(anyhow!(
839                "invalid genesis block"
840            )));
841        }
842
843        let untrusted = untrusted_block.as_untrusted_state();
844
845        // Prepare trusted state for verification. As we are using the verifier
846        // to verify the untrusted block and state transition, we must make
847        // sure that trusted and untrusted states don't belong to consecutive
848        // blocks as otherwise validator set hash will get verified also.
849        // Keeping heights at minimum distance of 2 will make sure that the
850        // verifier will check if there is enough overlap between the validator
851        // sets.
852        let header = trusted_block.signed_header.header;
853        let height = header.height;
854        let height = if height.increment() != untrusted.height() {
855            height
856        } else {
857            height
858                .value()
859                .checked_sub(1)
860                .ok_or_else(|| Error::ChainContextTransitionFailed(anyhow!("height underflow")))?
861                .try_into()
862                .unwrap()
863        };
864
865        let trusted = TrustedBlockState {
866            header_time: header.time,
867            height,
868            next_validators: &trusted_block.validators,
869            next_validators_hash: header.validators_hash,
870            // We need to use the target chain ID as we know it has changed.
871            chain_id: &chain_id(&host_info.consensus_chain_context),
872        };
873
874        // Verify the new block using +2/3 trust threshold rule.
875        let options = light_client::Options {
876            trust_threshold: TrustThreshold::TWO_THIRDS,
877            trusting_period: Duration::from_secs(3600 * 24 * 365 * 10), // 10 years
878            clock_drift: Duration::from_secs(60),
879        };
880        let now = clock.now();
881
882        let verdict = verifier.verify_update_header(untrusted, trusted, &options, now);
883
884        match verdict {
885            Verdict::Success => (),
886            Verdict::NotEnoughTrust(tally) => {
887                info!(
888                    self.logger,
889                    "Not enough trust to accept new chain context";
890                    "log_event" => "consensus/cometbft/verifier/chain_context/no_trust",
891                    "tally" => ?tally,
892                );
893                return Err(Error::ChainContextTransitionFailed(anyhow!(
894                    "not enough trust"
895                )));
896            }
897            Verdict::Invalid(e) => {
898                info!(
899                    self.logger,
900                    "Failed to accept new chain context";
901                    "log_event" => "consensus/cometbft/verifier/chain_context/failed",
902                    "error" => ?e,
903                );
904                return Err(Error::ChainContextTransitionFailed(anyhow!(
905                    "invalid genesis block"
906                )));
907            }
908        }
909
910        info!(self.logger, "Consensus chain context transition done");
911
912        let header = &untrusted_block.signed_header.header;
913        let trust_root = TrustRoot {
914            height: header.height.into(),
915            hash: header.hash().to_string(),
916            runtime_id: self.runtime_id,
917            chain_context: host_info.consensus_chain_context,
918        };
919
920        Ok(TrustedState {
921            trust_root,
922            trusted_blocks: vec![untrusted_block.into()],
923        })
924    }
925}