zcash_history/
node_data.rs

1use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
2use primitive_types::U256;
3
4use crate::Version;
5
6/// Maximum serialized size of the node metadata.
7pub const MAX_NODE_DATA_SIZE: usize = 32 + // subtree commitment
8    4 +  // start time
9    4 +  // end time
10    4 +  // start target
11    4 +  // end target
12    32 + // start sapling tree root
13    32 + // end sapling tree root
14    32 + // subtree total work
15    9 +  // start height (compact uint)
16    9 +  // end height (compact uint)
17    9 + // Sapling tx count (compact uint)
18    32 + // start Orchard tree root
19    32 + // end Orchard tree root
20    9; // Orchard tx count (compact uint)
21       // = total of 244
22
23/// V1 node metadata.
24#[repr(C)]
25#[derive(Debug, Clone, Default)]
26#[cfg_attr(test, derive(PartialEq, Eq))]
27pub struct NodeData {
28    /// Consensus branch id, should be provided by deserializing node.
29    pub consensus_branch_id: u32,
30    /// Subtree commitment - either block hash for leaves or hashsum of children for nodes.
31    pub subtree_commitment: [u8; 32],
32    /// Start time.
33    pub start_time: u32,
34    /// End time.
35    pub end_time: u32,
36    /// Start target.
37    pub start_target: u32,
38    /// End target.
39    pub end_target: u32,
40    /// Start sapling tree root.
41    pub start_sapling_root: [u8; 32],
42    /// End sapling tree root.
43    pub end_sapling_root: [u8; 32],
44    /// Part of tree total work.
45    pub subtree_total_work: U256,
46    /// Start height.
47    pub start_height: u64,
48    /// End height
49    pub end_height: u64,
50    /// Number of Sapling transactions.
51    pub sapling_tx: u64,
52}
53
54impl NodeData {
55    /// Combine two nodes metadata.
56    pub fn combine(left: &NodeData, right: &NodeData) -> NodeData {
57        crate::V1::combine(left, right)
58    }
59
60    pub(crate) fn combine_inner(
61        subtree_commitment: [u8; 32],
62        left: &NodeData,
63        right: &NodeData,
64    ) -> NodeData {
65        NodeData {
66            consensus_branch_id: left.consensus_branch_id,
67            subtree_commitment,
68            start_time: left.start_time,
69            end_time: right.end_time,
70            start_target: left.start_target,
71            end_target: right.end_target,
72            start_sapling_root: left.start_sapling_root,
73            end_sapling_root: right.end_sapling_root,
74            subtree_total_work: left.subtree_total_work + right.subtree_total_work,
75            start_height: left.start_height,
76            end_height: right.end_height,
77            sapling_tx: left.sapling_tx + right.sapling_tx,
78        }
79    }
80
81    fn write_compact<W: std::io::Write>(w: &mut W, compact: u64) -> std::io::Result<()> {
82        match compact {
83            0..=0xfc => w.write_all(&[compact as u8])?,
84            0xfd..=0xffff => {
85                w.write_all(&[0xfd])?;
86                w.write_u16::<LittleEndian>(compact as u16)?;
87            }
88            0x10000..=0xffff_ffff => {
89                w.write_all(&[0xfe])?;
90                w.write_u32::<LittleEndian>(compact as u32)?;
91            }
92            _ => {
93                w.write_all(&[0xff])?;
94                w.write_u64::<LittleEndian>(compact)?;
95            }
96        }
97        Ok(())
98    }
99
100    fn read_compact<R: std::io::Read>(reader: &mut R) -> std::io::Result<u64> {
101        let result = match reader.read_u8()? {
102            i @ 0..=0xfc => i.into(),
103            0xfd => reader.read_u16::<LittleEndian>()?.into(),
104            0xfe => reader.read_u32::<LittleEndian>()?.into(),
105            _ => reader.read_u64::<LittleEndian>()?,
106        };
107
108        Ok(result)
109    }
110
111    /// Write to the byte representation.
112    pub fn write<W: std::io::Write>(&self, w: &mut W) -> std::io::Result<()> {
113        w.write_all(&self.subtree_commitment)?;
114        w.write_u32::<LittleEndian>(self.start_time)?;
115        w.write_u32::<LittleEndian>(self.end_time)?;
116        w.write_u32::<LittleEndian>(self.start_target)?;
117        w.write_u32::<LittleEndian>(self.end_target)?;
118        w.write_all(&self.start_sapling_root)?;
119        w.write_all(&self.end_sapling_root)?;
120
121        let mut work_buf = [0u8; 32];
122        self.subtree_total_work.to_little_endian(&mut work_buf[..]);
123        w.write_all(&work_buf)?;
124
125        Self::write_compact(w, self.start_height)?;
126        Self::write_compact(w, self.end_height)?;
127        Self::write_compact(w, self.sapling_tx)?;
128        Ok(())
129    }
130
131    /// Read from the byte representation.
132    pub fn read<R: std::io::Read>(consensus_branch_id: u32, r: &mut R) -> std::io::Result<Self> {
133        let mut data = NodeData {
134            consensus_branch_id,
135            ..Default::default()
136        };
137        r.read_exact(&mut data.subtree_commitment)?;
138        data.start_time = r.read_u32::<LittleEndian>()?;
139        data.end_time = r.read_u32::<LittleEndian>()?;
140        data.start_target = r.read_u32::<LittleEndian>()?;
141        data.end_target = r.read_u32::<LittleEndian>()?;
142        r.read_exact(&mut data.start_sapling_root)?;
143        r.read_exact(&mut data.end_sapling_root)?;
144
145        let mut work_buf = [0u8; 32];
146        r.read_exact(&mut work_buf)?;
147        data.subtree_total_work = U256::from_little_endian(&work_buf);
148
149        data.start_height = Self::read_compact(r)?;
150        data.end_height = Self::read_compact(r)?;
151        data.sapling_tx = Self::read_compact(r)?;
152
153        Ok(data)
154    }
155
156    /// Convert to byte representation.
157    pub fn to_bytes(&self) -> Vec<u8> {
158        crate::V1::to_bytes(self)
159    }
160
161    /// Convert from byte representation.
162    pub fn from_bytes<T: AsRef<[u8]>>(consensus_branch_id: u32, buf: T) -> std::io::Result<Self> {
163        crate::V1::from_bytes(consensus_branch_id, buf)
164    }
165
166    /// Hash node metadata
167    pub fn hash(&self) -> [u8; 32] {
168        crate::V1::hash(self)
169    }
170}
171
172/// V2 node metadata.
173#[derive(Debug, Clone, Default)]
174#[cfg_attr(test, derive(PartialEq, Eq))]
175pub struct V2 {
176    /// The V1 node data retained in V2.
177    pub v1: NodeData,
178    /// Start Orchard tree root.
179    pub start_orchard_root: [u8; 32],
180    /// End Orchard tree root.
181    pub end_orchard_root: [u8; 32],
182    /// Number of Orchard transactions.
183    pub orchard_tx: u64,
184}
185
186impl V2 {
187    pub(crate) fn combine_inner(subtree_commitment: [u8; 32], left: &V2, right: &V2) -> V2 {
188        V2 {
189            v1: NodeData::combine_inner(subtree_commitment, &left.v1, &right.v1),
190            start_orchard_root: left.start_orchard_root,
191            end_orchard_root: right.end_orchard_root,
192            orchard_tx: left.orchard_tx + right.orchard_tx,
193        }
194    }
195
196    /// Write to the byte representation.
197    pub fn write<W: std::io::Write>(&self, w: &mut W) -> std::io::Result<()> {
198        self.v1.write(w)?;
199        w.write_all(&self.start_orchard_root)?;
200        w.write_all(&self.end_orchard_root)?;
201        NodeData::write_compact(w, self.orchard_tx)?;
202        Ok(())
203    }
204
205    /// Read from the byte representation.
206    pub fn read<R: std::io::Read>(consensus_branch_id: u32, r: &mut R) -> std::io::Result<Self> {
207        let mut data = V2 {
208            v1: NodeData::read(consensus_branch_id, r)?,
209            ..Default::default()
210        };
211        r.read_exact(&mut data.start_orchard_root)?;
212        r.read_exact(&mut data.end_orchard_root)?;
213        data.orchard_tx = NodeData::read_compact(r)?;
214
215        Ok(data)
216    }
217}
218
219#[cfg(any(test, feature = "test-dependencies"))]
220pub mod testing {
221    use primitive_types::U256;
222    use proptest::array::uniform32;
223    use proptest::prelude::{any, prop_compose};
224
225    use super::NodeData;
226
227    prop_compose! {
228        pub fn arb_node_data()(
229            subtree_commitment in uniform32(any::<u8>()),
230            start_time in any::<u32>(),
231            end_time in any::<u32>(),
232            start_target in any::<u32>(),
233            end_target in any::<u32>(),
234            start_sapling_root in uniform32(any::<u8>()),
235            end_sapling_root in uniform32(any::<u8>()),
236            subtree_total_work in uniform32(any::<u8>()),
237            start_height in any::<u64>(),
238            end_height in any::<u64>(),
239            sapling_tx in any::<u64>(),
240        ) -> NodeData {
241            NodeData {
242                consensus_branch_id: 0,
243                subtree_commitment,
244                start_time,
245                end_time,
246                start_target,
247                end_target,
248                start_sapling_root,
249                end_sapling_root,
250                subtree_total_work: U256::from_little_endian(&subtree_total_work[..]),
251                start_height,
252                end_height,
253                sapling_tx
254            }
255        }
256    }
257}
258
259#[cfg(test)]
260mod tests {
261    use super::testing::arb_node_data;
262    use proptest::prelude::*;
263
264    use super::NodeData;
265
266    proptest! {
267        #[test]
268        fn serialization_round_trip(node_data in arb_node_data()) {
269            assert_eq!(NodeData::from_bytes(0, node_data.to_bytes()).unwrap(), node_data);
270        }
271    }
272}