Skip to main content

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