ibc_client_tendermint_types/
header.rs1use core::fmt::{Display, Error as FmtError, Formatter};
4use core::str::FromStr;
5
6use ibc_core_client_types::error::ClientError;
7use ibc_core_client_types::Height;
8use ibc_core_host_types::error::DecodingError;
9use ibc_core_host_types::identifiers::ChainId;
10use ibc_primitives::prelude::*;
11use ibc_primitives::{IntoTimestamp, Timestamp};
12use ibc_proto::google::protobuf::Any;
13use ibc_proto::ibc::lightclients::tendermint::v1::Header as RawHeader;
14use ibc_proto::Protobuf;
15use pretty::{PrettySignedHeader, PrettyValidatorSet};
16use tendermint::block::signed_header::SignedHeader;
17use tendermint::chain::Id as TmChainId;
18use tendermint::crypto::Sha256;
19use tendermint::merkle::MerkleHash;
20use tendermint::validator::Set as ValidatorSet;
21use tendermint::{Hash, Time};
22use tendermint_light_client_verifier::types::{TrustedBlockState, UntrustedBlockState};
23
24use crate::error::TendermintClientError;
25
26pub const TENDERMINT_HEADER_TYPE_URL: &str = "/ibc.lightclients.tendermint.v1.Header";
27
28#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
29#[derive(Clone, PartialEq, Eq)]
31pub struct Header {
32 pub signed_header: SignedHeader, pub validator_set: ValidatorSet, pub trusted_height: Height, pub trusted_next_validator_set: ValidatorSet, }
37
38impl core::fmt::Debug for Header {
39 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
40 write!(f, " Header {{...}}")
41 }
42}
43
44impl Display for Header {
45 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
46 write!(f, "Header {{ signed_header: {}, validator_set: {}, trusted_height: {}, trusted_validator_set: {} }}", PrettySignedHeader(&self.signed_header), PrettyValidatorSet(&self.validator_set), self.trusted_height, PrettyValidatorSet(&self.trusted_next_validator_set))
47 }
48}
49
50impl Header {
51 pub fn timestamp(&self) -> Result<Timestamp, TendermintClientError> {
52 self.signed_header
53 .header
54 .time
55 .into_timestamp()
56 .map_err(Into::into)
57 }
58
59 pub fn height(&self) -> Height {
60 Height::new(
61 ChainId::from_str(self.signed_header.header.chain_id.as_str())
62 .expect("chain id")
63 .revision_number(),
64 u64::from(self.signed_header.header.height),
65 )
66 .expect("malformed tendermint header domain type has an illegal height of 0")
67 }
68
69 pub fn as_untrusted_block_state(&self) -> UntrustedBlockState<'_> {
70 UntrustedBlockState {
71 signed_header: &self.signed_header,
72 validators: &self.validator_set,
73 next_validators: None,
74 }
75 }
76
77 pub fn as_trusted_block_state<'a>(
78 &'a self,
79 chain_id: &'a TmChainId,
80 header_time: Time,
81 next_validators_hash: Hash,
82 ) -> Result<TrustedBlockState<'a>, TendermintClientError> {
83 Ok(TrustedBlockState {
84 chain_id,
85 header_time,
86 height: self
87 .trusted_height
88 .revision_height()
89 .try_into()
90 .map_err(|_| {
91 TendermintClientError::InvalidHeaderHeight(
92 self.trusted_height.revision_height(),
93 )
94 })?,
95 next_validators: &self.trusted_next_validator_set,
96 next_validators_hash,
97 })
98 }
99
100 pub fn verify_chain_id_version_matches_height(
101 &self,
102 chain_id: &ChainId,
103 ) -> Result<(), TendermintClientError> {
104 if self.height().revision_number() != chain_id.revision_number() {
105 return Err(TendermintClientError::MismatchedHeaderChainIds {
106 actual: self.signed_header.header.chain_id.to_string(),
107 expected: chain_id.to_string(),
108 });
109 }
110 Ok(())
111 }
112
113 pub fn check_trusted_next_validator_set<H: MerkleHash + Sha256 + Default>(
117 &self,
118 trusted_next_validator_hash: &Hash,
119 ) -> Result<(), ClientError> {
120 if &self.trusted_next_validator_set.hash_with::<H>() == trusted_next_validator_hash {
121 Ok(())
122 } else {
123 Err(ClientError::FailedToVerifyHeader {
124 description: "trusted next validator set hash does not match hash stored on chain"
125 .to_string(),
126 })
127 }
128 }
129
130 pub fn validate_basic<H: MerkleHash + Sha256 + Default>(
132 &self,
133 ) -> Result<(), TendermintClientError> {
134 if self.height().revision_number() != self.trusted_height.revision_number() {
135 return Err(TendermintClientError::MismatchedRevisionHeights {
136 expected: self.trusted_height.revision_number(),
137 actual: self.height().revision_number(),
138 });
139 }
140
141 if self.trusted_height >= self.height() {
146 return Err(TendermintClientError::InvalidHeaderHeight(
147 self.height().revision_height(),
148 ));
149 }
150
151 let validators_hash = self.validator_set.hash_with::<H>();
152
153 if validators_hash != self.signed_header.header.validators_hash {
154 return Err(TendermintClientError::MismatchedValidatorHashes {
155 expected: self.signed_header.header.validators_hash,
156 actual: validators_hash,
157 });
158 }
159
160 Ok(())
161 }
162}
163
164impl Protobuf<RawHeader> for Header {}
165
166impl TryFrom<RawHeader> for Header {
167 type Error = DecodingError;
168
169 fn try_from(raw: RawHeader) -> Result<Self, Self::Error> {
170 let header = Self {
171 signed_header: raw
172 .signed_header
173 .ok_or(DecodingError::missing_raw_data("signed header"))?
174 .try_into()
175 .map_err(|e| DecodingError::invalid_raw_data(format!("signed header: {e:?}")))?,
176 validator_set: raw
177 .validator_set
178 .ok_or(DecodingError::missing_raw_data("validator set"))?
179 .try_into()
180 .map_err(|e| DecodingError::invalid_raw_data(format!("validator set: {e:?}")))?,
181 trusted_height: raw
182 .trusted_height
183 .and_then(|raw_height| raw_height.try_into().ok())
184 .ok_or(DecodingError::missing_raw_data("trusted height"))?,
185 trusted_next_validator_set: raw
186 .trusted_validators
187 .ok_or(DecodingError::missing_raw_data(
188 "trusted next validator set",
189 ))?
190 .try_into()
191 .map_err(|e| {
192 DecodingError::invalid_raw_data(format!("trusted next validator set: {e:?}"))
193 })?,
194 };
195
196 Ok(header)
197 }
198}
199
200impl Protobuf<Any> for Header {}
201
202impl TryFrom<Any> for Header {
203 type Error = DecodingError;
204
205 fn try_from(raw: Any) -> Result<Self, Self::Error> {
206 if let TENDERMINT_HEADER_TYPE_URL = raw.type_url.as_str() {
207 Protobuf::<RawHeader>::decode(raw.value.as_ref()).map_err(Into::into)
208 } else {
209 Err(DecodingError::MismatchedResourceName {
210 expected: TENDERMINT_HEADER_TYPE_URL.to_string(),
211 actual: raw.type_url,
212 })
213 }
214 }
215}
216
217impl From<Header> for Any {
218 fn from(header: Header) -> Self {
219 Any {
220 type_url: TENDERMINT_HEADER_TYPE_URL.to_string(),
221 value: Protobuf::<RawHeader>::encode_vec(header),
222 }
223 }
224}
225
226impl From<Header> for RawHeader {
227 fn from(value: Header) -> Self {
228 RawHeader {
229 signed_header: Some(value.signed_header.into()),
230 validator_set: Some(value.validator_set.into()),
231 trusted_height: Some(value.trusted_height.into()),
232 trusted_validators: Some(value.trusted_next_validator_set.into()),
233 }
234 }
235}
236
237mod pretty {
238 use ibc_primitives::utils::PrettySlice;
239
240 pub use super::*;
241
242 pub struct PrettySignedHeader<'a>(pub &'a SignedHeader);
243
244 impl Display for PrettySignedHeader<'_> {
245 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
246 write!(
247 f,
248 "SignedHeader {{ header: {{ chain_id: {}, height: {} }}, commit: {{ height: {} }} }}",
249 self.0.header.chain_id, self.0.header.height, self.0.commit.height
250 )
251 }
252 }
253
254 pub struct PrettyValidatorSet<'a>(pub &'a ValidatorSet);
255
256 impl Display for PrettyValidatorSet<'_> {
257 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
258 let validator_addresses: Vec<_> = self
259 .0
260 .validators()
261 .iter()
262 .map(|validator| validator.address)
263 .collect();
264 if let Some(proposer) = self.0.proposer() {
265 match &proposer.name {
266 Some(name) => write!(f, "PrettyValidatorSet {{ validators: {}, proposer: {}, total_voting_power: {} }}", PrettySlice(&validator_addresses), name, self.0.total_voting_power()),
267 None => write!(f, "PrettyValidatorSet {{ validators: {}, proposer: None, total_voting_power: {} }}", PrettySlice(&validator_addresses), self.0.total_voting_power()),
268 }
269 } else {
270 write!(
271 f,
272 "PrettyValidatorSet {{ validators: {}, proposer: None, total_voting_power: {} }}",
273 PrettySlice(&validator_addresses),
274 self.0.total_voting_power()
275 )
276 }
277 }
278 }
279}