oasis_core_runtime/consensus/tendermint/
mod.rs

1//! Tendermint consensus layer backend.
2
3pub mod merkle;
4pub mod verifier;
5
6use std::convert::{TryFrom, TryInto};
7
8use anyhow::{anyhow, Result};
9use tendermint::{
10    block::signed_header::SignedHeader as TMSignedHeader, chain, validator::Set as TMValidatorSet,
11};
12use tendermint_proto::{types::LightBlock as RawLightBlock, Protobuf};
13
14use crate::{
15    common::{crypto::hash::Hash, namespace::Namespace},
16    consensus::LightBlock,
17    storage::mkvs::{Root, RootType},
18};
19
20/// Tendermint consensus backend name.
21/// Keep synced with go/consensus/cometbft/api/api.go.
22pub const BACKEND_NAME: &str = "tendermint";
23
24/// The domain separation context used by Oasis Core for Tendermint cryptography.
25/// Keep synced with go/consensus/cometbft/crypto/signature.go.
26pub const TENDERMINT_CONTEXT: &[u8] = b"oasis-core/tendermint";
27
28/// Convert an Oasis Core chain context into a Tendermint chain ID.
29pub fn chain_id(chain_context: &str) -> chain::Id {
30    chain_context[..chain::id::MAX_LENGTH].try_into().unwrap()
31}
32
33/// Decode the light block metadata as a Tendermint light block.
34pub fn decode_light_block(light_block: LightBlock) -> Result<LightBlockMeta> {
35    LightBlockMeta::decode_vec(&light_block.meta).map_err(|e| anyhow!("{}", e))
36}
37
38/// Encode the light block metadata to a Tendermint light block.
39pub fn encode_light_block(light_block_meta: LightBlockMeta) -> Result<LightBlock> {
40    let height = u64::from(
41        light_block_meta
42            .signed_header
43            .as_ref()
44            .ok_or_else(|| anyhow!("signed header should be present"))?
45            .header
46            .height,
47    );
48    let meta = LightBlockMeta::encode_vec(light_block_meta);
49
50    Ok(LightBlock { height, meta })
51}
52
53/// Extract state root from the given signed block header.
54///
55/// # Panics
56///
57/// The signed header must be present and the application hash must be a valid Oasis Core
58/// application hash (state root hash).
59pub fn state_root_from_header(signed_header: &TMSignedHeader) -> Root {
60    let header = signed_header.header();
61    let height: u64 = header.height.into();
62    let hash: [u8; 32] = header
63        .app_hash
64        .as_bytes()
65        .try_into()
66        .expect("invalid app hash");
67
68    Root {
69        namespace: Namespace::default(),
70        version: height - 1,
71        root_type: RootType::State,
72        hash: Hash(hash),
73    }
74}
75
76/// Tendermint light consensus block metadata.
77#[derive(Debug, Clone)]
78pub struct LightBlockMeta {
79    pub signed_header: Option<TMSignedHeader>,
80    pub validators: TMValidatorSet,
81}
82
83impl LightBlockMeta {
84    /// State root specified by this light block.
85    ///
86    /// # Panics
87    ///
88    /// The signed header must be present and the application hash must be a valid Oasis Core
89    /// application hash (state root hash).
90    pub fn get_state_root(&self) -> Root {
91        let header = self
92            .signed_header
93            .as_ref()
94            .expect("signed header should be present");
95
96        state_root_from_header(header)
97    }
98}
99
100impl Protobuf<RawLightBlock> for LightBlockMeta {}
101
102impl TryFrom<RawLightBlock> for LightBlockMeta {
103    type Error = anyhow::Error;
104
105    fn try_from(value: RawLightBlock) -> Result<Self> {
106        Ok(LightBlockMeta {
107            signed_header: value
108                .signed_header
109                .map(TryInto::try_into)
110                .transpose()
111                .map_err(|error| anyhow!("{}", error))?,
112            validators: value
113                .validator_set
114                .ok_or_else(|| anyhow!("missing validator set"))?
115                .try_into()
116                .map_err(|error| anyhow!("{}", error))?,
117        })
118    }
119}
120
121impl From<LightBlockMeta> for RawLightBlock {
122    fn from(value: LightBlockMeta) -> Self {
123        RawLightBlock {
124            signed_header: value.signed_header.map(Into::into),
125            validator_set: Some(value.validators.into()),
126        }
127    }
128}