Skip to main content

amaru_kernel/cardano/
block.rs

1// Copyright 2026 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::collections::{BTreeMap, BTreeSet};
16
17use crate::{
18    AuxiliaryData, Hash, Hasher, Header, HeaderHash, Transaction, TransactionBody, WitnessSet, cbor,
19    size::{BLOCK_BODY, HEADER},
20};
21
22#[derive(Debug, Clone, PartialEq, cbor::Encode, serde::Serialize, serde::Deserialize)]
23pub struct Block {
24    #[cbor(skip)]
25    original_body_size: u64,
26
27    #[cbor(skip)]
28    original_header_size: u64,
29
30    #[cbor(skip)]
31    hash: Hash<BLOCK_BODY>,
32
33    #[cbor(skip)]
34    header_hash: HeaderHash,
35
36    #[n(0)]
37    pub header: Header,
38
39    #[b(1)]
40    pub transaction_bodies: Vec<TransactionBody>,
41
42    #[n(2)]
43    pub transaction_witnesses: Vec<WitnessSet>,
44
45    #[n(3)]
46    pub auxiliary_data: BTreeMap<u32, AuxiliaryData>,
47
48    #[n(4)]
49    pub invalid_transactions: Option<BTreeSet<u32>>,
50}
51
52impl Block {
53    /// Get the size in bytes of the serialised block.
54    pub fn body_len(&self) -> u64 {
55        self.original_body_size
56    }
57
58    /// Get the size in bytes of the serialised block's header
59    pub fn header_len(&self) -> u64 {
60        self.original_header_size
61    }
62
63    pub fn header_hash(&self) -> HeaderHash {
64        self.header_hash
65    }
66}
67
68impl IntoIterator for Block {
69    type Item = (u32, Transaction);
70    type IntoIter = std::vec::IntoIter<Self::Item>;
71
72    fn into_iter(mut self) -> Self::IntoIter {
73        (0u32..)
74            .zip(self.transaction_bodies)
75            .zip(self.transaction_witnesses)
76            .map(|((i, body), witnesses)| {
77                let is_expected_valid =
78                    !self.invalid_transactions.as_ref().map(|set| set.contains(&i)).unwrap_or(false);
79
80                // TODO: Do not re-hash here, but get the hash while parsing.
81                let auxiliary_data: Option<AuxiliaryData> = self.auxiliary_data.remove(&i);
82
83                (i, Transaction { body, witnesses, is_expected_valid, auxiliary_data })
84            })
85            .collect::<Vec<_>>()
86            .into_iter()
87    }
88}
89
90// FIXME: Constraints & multi-era decoding
91//
92// There are various decoding rules that aren't enforced but that should be; for example (and
93// non-exhaustively):
94//
95// - indices are constrained by the maximum number of elements in each arrays
96// - there must be exactly the same number of witnesses and bodies
97// - ...
98//
99// Also, we will likely require multi-era decoding too here. Even if we don't expect blocks from
100// previous eras in normal operation (albeit, to be confirmed...), we will require to re-validate
101// that a given chain is indeed at least well-formed, and that means drilling through headers to
102// ensure they form a chain. So at least *some level* of multi-era decoding is necessary.
103impl<'b, C> cbor::Decode<'b, C> for Block {
104    fn decode(d: &mut cbor::Decoder<'b>, ctx: &mut C) -> Result<Self, cbor::decode::Error> {
105        cbor::heterogeneous_array(d, |d, assert_len| {
106            assert_len(5)?;
107
108            let (header, header_bytes) = cbor::tee(d, |d| d.decode_with(ctx))?;
109
110            let (transaction_bodies, transaction_bodies_bytes) = cbor::tee(d, |d| d.decode_with(ctx))?;
111
112            let (transaction_witnesses, transaction_witnesses_bytes) = cbor::tee(d, |d| d.decode_with(ctx))?;
113
114            let (auxiliary_data, auxiliary_data_bytes) = cbor::tee(d, |d| d.decode_with(ctx))?;
115
116            let (invalid_transactions, invalid_transactions_bytes) = cbor::tee(d, |d| d.decode_with(ctx))?;
117
118            let mut block_body_hash = Vec::with_capacity(4 * BLOCK_BODY);
119            for component in [
120                transaction_bodies_bytes,
121                transaction_witnesses_bytes,
122                auxiliary_data_bytes,
123                invalid_transactions_bytes,
124            ] {
125                let body_part = Hasher::<{ 8 * BLOCK_BODY }>::hash(component);
126                block_body_hash.extend_from_slice(&body_part[..]);
127            }
128
129            Ok(Block {
130                original_body_size: (transaction_bodies_bytes.len()
131                    + transaction_witnesses_bytes.len()
132                    + auxiliary_data_bytes.len()
133                    + invalid_transactions_bytes.len()) as u64,
134                original_header_size: header_bytes.len() as u64,
135                hash: Hasher::<{ 8 * BLOCK_BODY }>::hash(&block_body_hash[..]),
136                header_hash: Hasher::<{ 8 * HEADER }>::hash(header_bytes),
137                header,
138                transaction_bodies,
139                transaction_witnesses,
140                auxiliary_data,
141                invalid_transactions,
142            })
143        })
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use test_case::test_case;
150
151    use super::*;
152    use crate::EraName;
153
154    macro_rules! fixture {
155        ($id:expr) => {{
156            (
157                Hash::from(&hex::decode($id).unwrap()[..]),
158                $crate::try_include_cbor!(concat!("cbor.decode/block/", $id, "/sample.cbor")),
159            )
160        }};
161    }
162
163    #[test_case(
164        70175999,
165        fixture!("b9bef52dd8dedf992837d20c18399a284d80fde0ae9435f2a33649aaee7c5698")
166    )]
167    #[test_case(
168        70206662,
169        fixture!("b99a61170fcdb5bade252be2cb0fa6e3ac550b9f5cc4e9d001eda88291eb9de7")
170    )]
171    #[test_case(
172        70225763,
173        fixture!("313e774e32c23b3691751e62d6b57181538cf3164b242505919bce29226de19f")
174    )]
175    #[test_case(
176        70582226,
177        fixture!("e1b90d83d6ae89860e2d1a0f398355cd4ed6defddb028dd610748d1f5610b546")
178    )]
179    #[test_case(
180        71419349,
181        fixture!("0df40008e40348c40cdc3b92a1e31d0e55675ddf2bb05ff7683b38a837048bca")
182    )]
183    fn decode_wellformed(slot: u64, (id, result): (Hash<HEADER>, Result<(EraName, Block), cbor::decode::Error>)) {
184        match result {
185            Err(err) => panic!("{err}"),
186            Ok((era_version, block)) => {
187                assert_eq!(era_version, EraName::Conway);
188
189                assert_eq!(hex::encode(&block.hash[..]), hex::encode(&block.header.header_body.block_body_hash[..]),);
190
191                assert_eq!(block.header_hash, id);
192                assert_eq!(block.header.header_body.slot, slot);
193            }
194        }
195    }
196}