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