Skip to content

Commit e19359d

Browse files
committed
Implement borsh serialization
1 parent ce8d534 commit e19359d

File tree

5 files changed

+208
-4
lines changed

5 files changed

+208
-4
lines changed

.github/workflows/rust.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,14 @@ jobs:
3333
- name: Clippy (with tests)
3434
run: cargo clippy --verbose --tests
3535
- name: Clippy (with tests + borsh)
36-
run: cargo clippy --verbose --tests --benches
36+
run: cargo clippy --verbose --tests --benches --features borsh
3737

3838
test:
3939
runs-on: ubuntu-latest
4040
steps:
4141
- uses: actions/checkout@v4
4242
- name: Run tests
43-
run: cargo test --verbose
43+
run: cargo test --verbose --features borsh
4444

4545
test-no-std:
4646
runs-on: ubuntu-latest
@@ -54,4 +54,4 @@ jobs:
5454
steps:
5555
- uses: actions/checkout@v4
5656
- name: Generate docs
57-
run: cargo doc --verbose
57+
run: cargo doc --verbose --features borsh

Cargo.lock

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@ categories = ["algorithms", "data-structures"]
1515

1616
[features]
1717
default = ["std", "display-error"]
18-
std = ["thiserror/std", "slab/std"]
18+
borsh = ["dep:borsh"]
19+
std = ["thiserror/std", "slab/std", "borsh/std"]
1920
display-error = ["dep:thiserror"]
2021
test-dependencies = ["proptest", "incrementalmerkletree/test-dependencies"]
2122

2223
[dependencies]
24+
borsh = { version = "1.5.7", default-features = false, optional = true }
2325
incrementalmerkletree = { version = "0.8", default-features = false }
2426
proptest = { version = "1", optional = true }
2527
slab = { version = "0.4.10", default-features = false }

src/borsh_impl.rs

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
use std::io::{self, Read, Write};
2+
3+
use borsh::{BorshDeserialize, BorshSerialize};
4+
5+
use super::*;
6+
7+
impl<H, const DEPTH: u8> BorshDeserialize for BridgeTree<H, DEPTH>
8+
where
9+
H: BorshDeserialize,
10+
{
11+
fn deserialize_reader<R: Read>(reader: &mut R) -> io::Result<Self> {
12+
fn deserialize_u32_as_usize<R: Read>(reader: &mut R) -> io::Result<usize> {
13+
let x = u32::deserialize_reader(reader)?;
14+
usize::try_from(x).map_err(io::Error::other)
15+
}
16+
17+
fn deserialize_frontier<H: BorshDeserialize, R: Read>(
18+
reader: &mut R,
19+
) -> io::Result<NonEmptyFrontier<H>> {
20+
let frontier_position = Position::from(u64::deserialize_reader(reader)?);
21+
22+
let frontier_leaf = H::deserialize_reader(reader)?;
23+
24+
let frontier_ommers_len = deserialize_u32_as_usize(reader)?;
25+
let mut frontier_ommers = Vec::with_capacity(frontier_ommers_len);
26+
for _ in 0..frontier_ommers_len {
27+
frontier_ommers.push(H::deserialize_reader(reader)?);
28+
}
29+
30+
NonEmptyFrontier::from_parts(frontier_position, frontier_leaf, frontier_ommers).map_err(
31+
|err| {
32+
io::Error::other(format!(
33+
"failed to rebuild NonEmptyFrontier from deserialized \
34+
data: {err:?}"
35+
))
36+
},
37+
)
38+
}
39+
40+
// `frontier`
41+
let has_leaves = bool::deserialize_reader(reader)?;
42+
if !has_leaves {
43+
return Ok(Self::new());
44+
}
45+
let frontier = deserialize_frontier(reader)?;
46+
47+
// `tracking`
48+
let tracking_len = deserialize_u32_as_usize(reader)?;
49+
let mut tracking = BTreeSet::new();
50+
for _ in 0..tracking_len {
51+
let level = u8::deserialize_reader(reader)?;
52+
let index = u64::deserialize_reader(reader)?;
53+
tracking.insert(Address::from_parts(level.into(), index));
54+
}
55+
56+
// `ommers`
57+
let ommers_len = deserialize_u32_as_usize(reader)?;
58+
let mut ommers = BTreeMap::new();
59+
for _ in 0..ommers_len {
60+
let level = u8::deserialize_reader(reader)?;
61+
let index = u64::deserialize_reader(reader)?;
62+
let ommer = H::deserialize_reader(reader)?;
63+
ommers.insert(Address::from_parts(level.into(), index), ommer);
64+
}
65+
66+
// `bridges`
67+
let bridges_len = deserialize_u32_as_usize(reader)?;
68+
let mut bridges = Vec::with_capacity(bridges_len);
69+
for _ in 0..bridges_len {
70+
bridges.push(deserialize_frontier(reader)?);
71+
}
72+
73+
Self::from_parts(Some(frontier), bridges, tracking, ommers).map_err(|err| {
74+
io::Error::other(format!(
75+
"failed to rebuild BridgeTree from deserialized \
76+
data: {err:?}"
77+
))
78+
})
79+
}
80+
}
81+
82+
impl<H, const DEPTH: u8> BorshSerialize for BridgeTree<H, DEPTH>
83+
where
84+
H: BorshSerialize,
85+
{
86+
fn serialize<W: Write>(&self, writer: &mut W) -> io::Result<()> {
87+
fn serialize_usize_as_u32<W: Write>(x: usize, writer: &mut W) -> io::Result<()> {
88+
let x = u32::try_from(x).map_err(io::Error::other)?;
89+
BorshSerialize::serialize(&x, writer)
90+
}
91+
92+
fn serialize_frontier<H: BorshSerialize, W: Write>(
93+
frontier: &NonEmptyFrontier<H>,
94+
writer: &mut W,
95+
) -> io::Result<()> {
96+
BorshSerialize::serialize(&u64::from(frontier.position()), writer)?;
97+
BorshSerialize::serialize(frontier.leaf(), writer)?;
98+
99+
serialize_usize_as_u32(frontier.ommers().len(), writer)?;
100+
for ommer in frontier.ommers() {
101+
BorshSerialize::serialize(ommer, writer)?;
102+
}
103+
104+
Ok(())
105+
}
106+
107+
let Self {
108+
frontier,
109+
tracking,
110+
ommers,
111+
prior_bridges_slab: _,
112+
prior_bridges_slab_keys,
113+
} = self;
114+
115+
let Some(frontier) = frontier else {
116+
// `frontier`
117+
return BorshSerialize::serialize(&false, writer);
118+
};
119+
120+
// `frontier`
121+
BorshSerialize::serialize(&true, writer)?;
122+
serialize_frontier(frontier, writer)?;
123+
124+
// `tracking`
125+
serialize_usize_as_u32(tracking.len(), writer)?;
126+
for addr in tracking {
127+
BorshSerialize::serialize(&u8::from(addr.level()), writer)?;
128+
BorshSerialize::serialize(&addr.index(), writer)?;
129+
}
130+
131+
// `ommers`
132+
serialize_usize_as_u32(ommers.len(), writer)?;
133+
for (addr, ommer) in ommers {
134+
BorshSerialize::serialize(&u8::from(addr.level()), writer)?;
135+
BorshSerialize::serialize(&addr.index(), writer)?;
136+
BorshSerialize::serialize(ommer, writer)?;
137+
}
138+
139+
// `bridges`
140+
serialize_usize_as_u32(prior_bridges_slab_keys.len(), writer)?;
141+
for frontier in self.prior_bridges() {
142+
serialize_frontier(frontier, writer)?;
143+
}
144+
145+
Ok(())
146+
}
147+
}
148+
149+
#[cfg(test)]
150+
mod tests {
151+
use incrementalmerkletree_testing::TestHashable;
152+
153+
use super::*;
154+
155+
#[test]
156+
fn test_bridge_tree_borsh_roundtrip() {
157+
let mut tree = BridgeTree::<String, 4>::new();
158+
159+
// test empty tree
160+
let serialized = borsh::to_vec(&tree).unwrap();
161+
let deserialized = BridgeTree::try_from_slice(&serialized).unwrap();
162+
assert_eq!(BridgeTree::<String, 4>::new(), deserialized);
163+
164+
// test non-empty tree
165+
tree.append(String::from_u64(0u64)).unwrap();
166+
tree.mark().unwrap();
167+
tree.append(String::from_u64(1u64)).unwrap();
168+
tree.append(String::from_u64(2u64)).unwrap();
169+
tree.append(String::from_u64(3u64)).unwrap();
170+
tree.mark().unwrap();
171+
tree.append(String::from_u64(4u64)).unwrap();
172+
tree.append(String::from_u64(5u64)).unwrap();
173+
tree.mark().unwrap();
174+
tree.append(String::from_u64(6u64)).unwrap();
175+
tree.append(String::from_u64(7u64)).unwrap();
176+
tree.append(String::from_u64(8u64)).unwrap();
177+
tree.append(String::from_u64(9u64)).unwrap();
178+
179+
let serialized = borsh::to_vec(&tree).unwrap();
180+
let deserialized = BridgeTree::try_from_slice(&serialized).unwrap();
181+
assert_eq!(tree, deserialized);
182+
}
183+
}

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929

3030
extern crate alloc;
3131

32+
#[cfg(all(feature = "std", feature = "borsh"))]
33+
mod borsh_impl;
34+
3235
use alloc::collections::{BTreeMap, BTreeSet};
3336
use alloc::vec::Vec;
3437
use core::fmt::Debug;

0 commit comments

Comments
 (0)