zcash_client_backend/serialization/
shardtree.rs1use byteorder::{ReadBytesExt, WriteBytesExt};
4use core::ops::Deref;
5use shardtree::{Node, PrunableTree, RetentionFlags, Tree};
6use std::io::{self, Read, Write};
7use std::sync::Arc;
8use zcash_encoding::Optional;
9use zcash_primitives::merkle_tree::HashSer;
10
11const SER_V1: u8 = 1;
12
13const NIL_TAG: u8 = 0;
14const LEAF_TAG: u8 = 1;
15const PARENT_TAG: u8 = 2;
16
17pub fn write_shard<H: HashSer, W: Write>(writer: &mut W, tree: &PrunableTree<H>) -> io::Result<()> {
22 fn write_inner<H: HashSer, W: Write>(
23 mut writer: &mut W,
24 tree: &PrunableTree<H>,
25 ) -> io::Result<()> {
26 match tree.deref() {
27 Node::Parent { ann, left, right } => {
28 writer.write_u8(PARENT_TAG)?;
29 Optional::write(&mut writer, ann.as_ref(), |w, h| {
30 <H as HashSer>::write(h, w)
31 })?;
32 write_inner(writer, left)?;
33 write_inner(writer, right)?;
34 Ok(())
35 }
36 Node::Leaf { value } => {
37 writer.write_u8(LEAF_TAG)?;
38 value.0.write(&mut writer)?;
39 writer.write_u8(value.1.bits())?;
40 Ok(())
41 }
42 Node::Nil => {
43 writer.write_u8(NIL_TAG)?;
44 Ok(())
45 }
46 }
47 }
48
49 writer.write_u8(SER_V1)?;
50 write_inner(writer, tree)
51}
52
53fn read_shard_v1<H: HashSer, R: Read>(mut reader: &mut R) -> io::Result<PrunableTree<H>> {
54 match reader.read_u8()? {
55 PARENT_TAG => {
56 let ann = Optional::read(&mut reader, <H as HashSer>::read)?.map(Arc::new);
57 let left = read_shard_v1(reader)?;
58 let right = read_shard_v1(reader)?;
59 Ok(Tree::parent(ann, left, right))
60 }
61 LEAF_TAG => {
62 let value = <H as HashSer>::read(&mut reader)?;
63 let flags = reader.read_u8().and_then(|bits| {
64 RetentionFlags::from_bits(bits).ok_or_else(|| {
65 io::Error::new(
66 io::ErrorKind::InvalidData,
67 format!(
68 "Byte value {bits} does not correspond to a valid set of retention flags"
69 ),
70 )
71 })
72 })?;
73 Ok(Tree::leaf((value, flags)))
74 }
75 NIL_TAG => Ok(Tree::empty()),
76 other => Err(io::Error::new(
77 io::ErrorKind::InvalidData,
78 format!("Node tag not recognized: {other}"),
79 )),
80 }
81}
82
83pub fn read_shard<H: HashSer, R: Read>(mut reader: R) -> io::Result<PrunableTree<H>> {
89 match reader.read_u8()? {
90 SER_V1 => read_shard_v1(&mut reader),
91 other => Err(io::Error::new(
92 io::ErrorKind::InvalidData,
93 format!("Shard serialization version not recognized: {other}"),
94 )),
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use incrementalmerkletree::frontier::testing::{TestNode, arb_test_node};
101 use proptest::prelude::*;
102 use shardtree::testing::arb_prunable_tree;
103 use std::io::Cursor;
104
105 use super::{read_shard, write_shard};
106
107 proptest! {
108 #[test]
109 fn check_shard_roundtrip(
110 tree in arb_prunable_tree(arb_test_node(), 8, 32)
111 ) {
112 let mut tree_data = vec![];
113 write_shard(&mut tree_data, &tree).unwrap();
114 let cursor = Cursor::new(tree_data);
115 let tree_result = read_shard::<TestNode, _>(cursor).unwrap();
116 assert_eq!(tree, tree_result);
117 }
118 }
119}