dig_network_block/
body.rs

1//! L2 block body: application data and standardized emissions.
2//!
3//! The body owns calculation of its `BODY_ROOT` by composing the two subroots:
4//! - `DATA_ROOT`: Merkle root of per-byte data item hashes.
5//! - `EMISSIONS_ROOT`: Merkle root of per-emission hashes.
6//!
7//! Both collections are sorted by their hash for determinism (sorting is done on
8//! a local copy so `calculate_*` methods do not mutate the body).
9
10use crate::dig_l2_definition as definitions;
11use crate::emission::Emission;
12use serde::{Deserialize, Serialize};
13use thiserror::Error;
14
15/// Body of an L2 block: application data bytes and reward emissions.
16#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
17pub struct L2BlockBody {
18    /// Application-specific data bytes. Serialized as `0x`-prefixed hex string.
19    #[serde(with = "crate::serde_hex::hex_vec")]
20    pub data: Vec<u8>,
21    /// Reward distribution records.
22    pub emissions: Vec<Emission>,
23}
24
25impl L2BlockBody {
26    /// Computes the `DATA_ROOT` as the Merkle root of `COMPUTE_DATA_HASH(byte)`
27    /// for each `byte` in `self.data`, sorted by hash ascending for determinism.
28    pub fn calculate_data_root(&self) -> definitions::Hash32 {
29        let mut leaves: Vec<definitions::Hash32> = self
30            .data
31            .iter()
32            .map(|b| definitions::COMPUTE_DATA_HASH(*b))
33            .collect();
34        leaves.sort_unstable();
35        definitions::MERKLE_ROOT(&leaves)
36    }
37
38    /// Computes the `EMISSIONS_ROOT` as the Merkle root of each emission's
39    /// per-item hash, sorted by hash ascending for determinism.
40    pub fn calculate_emissions_root(&self) -> definitions::Hash32 {
41        let mut leaves: Vec<definitions::Hash32> =
42            self.emissions.iter().map(|e| e.calculate_root()).collect();
43        leaves.sort_unstable();
44        definitions::MERKLE_ROOT(&leaves)
45    }
46
47    /// Computes the overall `BODY_ROOT` from the two subroots.
48    pub fn calculate_root(&self) -> definitions::Hash32 {
49        let d = self.calculate_data_root();
50        let e = self.calculate_emissions_root();
51        definitions::COMPUTE_BODY_ROOT(&d, &e)
52    }
53}
54
55/// Errors that can be returned by body-level operations.
56#[derive(Debug, Error)]
57pub enum BodyError {
58    /// Placeholder for future validation errors.
59    #[error("body error: {0}")]
60    Generic(String),
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66    use crate::emission::Emission;
67
68    #[test]
69    fn data_root_does_not_depend_on_input_order() {
70        let b1 = L2BlockBody {
71            data: vec![3, 1, 2],
72            emissions: vec![],
73        };
74        let b2 = L2BlockBody {
75            data: vec![2, 3, 1],
76            emissions: vec![],
77        };
78        assert_eq!(b1.calculate_data_root(), b2.calculate_data_root());
79    }
80
81    #[test]
82    fn emissions_root_does_not_depend_on_input_order() {
83        let e1 = Emission {
84            pubkey: [1u8; 48],
85            weight: 5,
86        };
87        let e2 = Emission {
88            pubkey: [2u8; 48],
89            weight: 5,
90        };
91        let e3 = Emission {
92            pubkey: [3u8; 48],
93            weight: 6,
94        };
95        let b1 = L2BlockBody {
96            data: vec![],
97            emissions: vec![e1.clone(), e2.clone(), e3.clone()],
98        };
99        let b2 = L2BlockBody {
100            data: vec![],
101            emissions: vec![e3, e1, e2],
102        };
103        assert_eq!(b1.calculate_emissions_root(), b2.calculate_emissions_root());
104    }
105
106    #[test]
107    fn body_root_changes_when_subroots_change() {
108        let e = Emission {
109            pubkey: [9u8; 48],
110            weight: 1,
111        };
112        let b1 = L2BlockBody {
113            data: vec![1, 2],
114            emissions: vec![e.clone()],
115        };
116        let b2 = L2BlockBody {
117            data: vec![1],
118            emissions: vec![e],
119        };
120        assert_ne!(b1.calculate_root(), b2.calculate_root());
121    }
122}