Skip to main content

amaru_kernel/cardano/
block_header.rs

1// Copyright 2025 PRAGMA
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::{
16    cmp::Ordering,
17    fmt::{self, Debug, Display, Formatter},
18};
19
20use crate::{
21    BlockHeight, Hasher, Header, HeaderBody, HeaderHash, IsHeader, Point, Slot, Tip,
22    cbor::{self},
23    size::HEADER,
24};
25
26#[cfg(any(test, feature = "test-utils"))]
27mod tests;
28
29#[cfg(any(test, feature = "test-utils"))]
30pub use tests::*;
31
32/// This header type encapsulates a header and its hash to avoid recomputing
33#[derive(PartialEq, Eq, Clone)]
34pub struct BlockHeader {
35    header: Header,
36    hash: HeaderHash,
37}
38
39impl Display for BlockHeader {
40    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
41        f.write_str(&format!(
42            "{}. {}{}",
43            self.slot(),
44            self.hash(),
45            self.parent_hash().map(|p| format!(" ({p})")).unwrap_or_default()
46        ))?;
47        Ok(())
48    }
49}
50
51/// We serialize both the hash and the header, but we use serde's flattening
52/// to avoid nesting the header inside another object.
53impl serde::Serialize for BlockHeader {
54    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
55    where
56        S: serde::Serializer,
57    {
58        #[derive(serde::Serialize)]
59        struct BlockHeaderSer<'a> {
60            hash: &'a HeaderHash,
61            #[serde(flatten)]
62            header: &'a Header,
63        }
64
65        let helper = BlockHeaderSer { hash: &self.hash, header: &self.header };
66
67        helper.serialize(serializer)
68    }
69}
70
71impl<'de> serde::Deserialize<'de> for BlockHeader {
72    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
73    where
74        D: serde::Deserializer<'de>,
75    {
76        let header = Header::deserialize(deserializer)?;
77        Ok(BlockHeader::from(header))
78    }
79}
80
81impl Debug for BlockHeader {
82    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
83        f.debug_struct("BlockHeader")
84            .field("hash", &hex::encode(self.hash()))
85            .field("slot", &self.slot().as_u64())
86            .field("parent", &self.parent().map(hex::encode))
87            .finish()
88    }
89}
90
91impl PartialOrd for BlockHeader {
92    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
93        Some(self.cmp(other))
94    }
95}
96
97impl Ord for BlockHeader {
98    fn cmp(&self, other: &Self) -> Ordering {
99        self.point().cmp(&other.point())
100    }
101}
102
103impl core::hash::Hash for BlockHeader {
104    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
105        self.hash.hash(state);
106    }
107}
108
109impl BlockHeader {
110    /// Create a new BlockHeader from a Header and its precomputed hash
111    /// Note: The hash is not verified to match the header!
112    #[cfg(feature = "test-utils")]
113    pub fn new(header: Header, hash: HeaderHash) -> Self {
114        Self { header, hash }
115    }
116
117    pub fn header(&self) -> &Header {
118        &self.header
119    }
120
121    pub fn header_body(&self) -> &HeaderBody {
122        &self.header.header_body
123    }
124
125    pub fn parent_hash(&self) -> Option<HeaderHash> {
126        self.header.header_body.prev_hash
127    }
128
129    fn recompute_hash(&mut self) {
130        self.hash = Hasher::<{ HEADER * 8 }>::hash_cbor(&self.header);
131    }
132
133    pub fn tip(&self) -> Tip {
134        Tip::new(self.point(), self.block_height())
135    }
136}
137
138impl<C> cbor::Encode<C> for BlockHeader {
139    fn encode<W: cbor::encode::Write>(
140        &self,
141        e: &mut cbor::Encoder<W>,
142        ctx: &mut C,
143    ) -> Result<(), cbor::encode::Error<W::Error>> {
144        self.header.encode(e, ctx)
145    }
146}
147
148impl<'b, C> cbor::Decode<'b, C> for BlockHeader {
149    fn decode(d: &mut cbor::Decoder<'b>, ctx: &mut C) -> Result<Self, cbor::decode::Error> {
150        let header = Header::decode(d, ctx)?;
151        Ok(BlockHeader::from(header))
152    }
153}
154
155impl From<Header> for BlockHeader {
156    fn from(header: Header) -> Self {
157        let hash = Point::Origin.hash();
158        let mut block_header = Self { header, hash };
159        block_header.recompute_hash();
160        block_header
161    }
162}
163
164impl From<&Header> for BlockHeader {
165    fn from(header: &Header) -> Self {
166        let hash = Point::Origin.hash();
167        let mut block_header = Self { header: header.clone(), hash };
168        block_header.recompute_hash();
169        block_header
170    }
171}
172
173/// Concrete Conway-era compatible `Header` implementation.
174///
175/// There's no difference in headers' structure between Babbage
176/// and Conway era. The idea is that we only keep concrete the header from
177/// the latest era, and convert other headers on the fly when needed.
178impl IsHeader for BlockHeader {
179    fn hash(&self) -> HeaderHash {
180        self.hash
181    }
182
183    fn parent(&self) -> Option<HeaderHash> {
184        self.header.header_body.prev_hash
185    }
186
187    fn block_height(&self) -> BlockHeight {
188        self.header.header_body.block_number.into()
189    }
190
191    fn slot(&self) -> Slot {
192        self.header.header_body.slot.into()
193    }
194
195    fn extended_vrf_nonce_output(&self) -> Vec<u8> {
196        self.header.header_body.nonce_vrf_output()
197    }
198}