1use std::{
4 collections::{BTreeMap, BTreeSet},
5 io::{Read, Write},
6 ops::Range,
7};
8
9use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
10
11use incrementalmerkletree::{Hashable, Position};
12use shardtree::{
13 LocatedPrunableTree, ShardTree,
14 store::{Checkpoint, ShardStore, TreeState, memory::MemoryShardStore},
15};
16use zcash_client_backend::serialization::shardtree::{read_shard, write_shard};
17use zcash_encoding::{Optional, Vector};
18use zcash_primitives::{
19 block::BlockHash,
20 merkle_tree::HashSer,
21 transaction::{Transaction, TxId},
22};
23use zcash_protocol::{
24 consensus::{self, BlockHeight},
25 memo::Memo,
26 value::Zatoshis,
27};
28use zcash_transparent::address::Script;
29
30use zcash_transparent::keys::NonHardenedChildIndex;
31use zingo_status::confirmation_status::ConfirmationStatus;
32
33use crate::{
34 keys::{
35 KeyId, decode_unified_address,
36 transparent::{TransparentAddressId, TransparentScope},
37 },
38 sync::{SHARDTREE_CHECKPOINT_ROLLING_WINDOW_SIZE, ScanPriority, ScanRange},
39 wallet::{ScanTarget, ScanTargetOrigin, ScanTargetPools},
40};
41
42use super::{
43 InitialSyncState, IronwoodNote, KeyIdInterface, NullifierMap, OrchardNote,
44 OutgoingIronwoodNote, OutgoingNote, OutgoingNoteInterface, OutgoingOrchardNote,
45 OutgoingSaplingNote, OutputId, OutputInterface, SaplingNote, ShardTrees, SyncState,
46 TransparentCoin, TreeBounds, WalletBlock, WalletNote, WalletTransaction,
47};
48
49fn read_string<R: Read>(mut reader: R) -> std::io::Result<String> {
50 let str_len = reader.read_u64::<LittleEndian>()?;
51 let mut str_bytes = vec![0; str_len as usize];
52 reader.read_exact(&mut str_bytes)?;
53
54 String::from_utf8(str_bytes)
55 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))
56}
57
58fn write_string<W: Write>(mut writer: W, str: &str) -> std::io::Result<()> {
59 writer.write_u64::<LittleEndian>(str.len() as u64)?;
60 writer.write_all(str.as_bytes())
61}
62
63pub(crate) fn read_version<R: Read>(
68 mut reader: R,
69 type_name: &str,
70 current_version: u8,
71) -> std::io::Result<u8> {
72 let version = reader.read_u8()?;
73 if version > current_version {
74 return Err(std::io::Error::new(
75 std::io::ErrorKind::InvalidData,
76 format!(
77 "{type_name} serialized version {version} was written by a newer build. this build reads up to \
78 version {current_version}."
79 ),
80 ));
81 }
82
83 Ok(version)
84}
85
86impl ScanTarget {
87 fn serialized_version() -> u8 {
88 1
91 }
92
93 const INTERNAL_ORIGIN: u8 = 0;
94 const EXTERNAL_ORIGIN: u8 = 1;
95
96 pub fn read<R: Read>(mut reader: R) -> std::io::Result<Self> {
98 let version = read_version(&mut reader, "ScanTarget", Self::serialized_version())?;
99 let block_height = BlockHeight::from_u32(reader.read_u32::<LittleEndian>()?);
100 if version == 0 {
101 let txid = TxId::read(&mut reader)?;
105 let _narrow_scan_area = reader.read_u8()?;
106 return Ok(Self::new(
107 block_height,
108 (txid != TxId::from_bytes([0; 32])).then_some(txid),
109 ));
110 }
111 let txid = Optional::read(&mut reader, TxId::read)?;
112 let pools = ScanTargetPools::from_bits(reader.read_u8()?).ok_or_else(|| {
113 std::io::Error::new(
114 std::io::ErrorKind::InvalidData,
115 "scan target names a shielded pool this version does not know",
116 )
117 })?;
118 let origin = match reader.read_u8()? {
119 Self::INTERNAL_ORIGIN => ScanTargetOrigin::Internal,
120 Self::EXTERNAL_ORIGIN => ScanTargetOrigin::External,
121 unknown => {
122 return Err(std::io::Error::new(
123 std::io::ErrorKind::InvalidData,
124 format!("unknown scan target origin {unknown}"),
125 ));
126 }
127 };
128
129 Ok(Self {
130 block_height,
131 txid,
132 pools,
133 origin,
134 })
135 }
136
137 pub fn write<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
139 writer.write_u8(Self::serialized_version())?;
140 writer.write_u32::<LittleEndian>(self.block_height().into())?;
141 Optional::write(&mut *writer, self.txid(), |w, txid| txid.write(w))?;
142 writer.write_u8(self.pools().bits())?;
143 writer.write_u8(match self.origin() {
144 ScanTargetOrigin::Internal => Self::INTERNAL_ORIGIN,
145 ScanTargetOrigin::External => Self::EXTERNAL_ORIGIN,
146 })
147 }
148}
149
150impl SyncState {
151 fn serialized_version() -> u8 {
152 5
155 }
156
157 pub fn read<R: Read>(mut reader: R) -> std::io::Result<Self> {
159 let version = read_version(&mut reader, "SyncState", Self::serialized_version())?;
160 let scan_ranges = Vector::read(&mut reader, |r| {
161 let start = BlockHeight::from_u32(r.read_u32::<LittleEndian>()?);
162 let end = BlockHeight::from_u32(r.read_u32::<LittleEndian>()?);
163 let priority = match version {
164 3.. => match r.read_u8()? {
165 0 => Ok(ScanPriority::RefetchingNullifiers),
166 1 => Ok(ScanPriority::Scanning),
167 2 => Ok(ScanPriority::Scanned),
168 3 => Ok(ScanPriority::ScannedWithoutMapping),
169 4 => Ok(ScanPriority::Historic),
170 5 => Ok(ScanPriority::OpenAdjacent),
171 6 => Ok(ScanPriority::FoundNote),
172 7 => Ok(ScanPriority::ChainTip),
173 8 => Ok(ScanPriority::Verify),
174 _ => Err(std::io::Error::new(
175 std::io::ErrorKind::InvalidData,
176 "invalid scan priority",
177 )),
178 }?,
179 2 => match r.read_u8()? {
180 0 => Ok(ScanPriority::Scanning),
181 1 => Ok(ScanPriority::Scanned),
182 2 => Ok(ScanPriority::ScannedWithoutMapping),
183 3 => Ok(ScanPriority::Historic),
184 4 => Ok(ScanPriority::OpenAdjacent),
185 5 => Ok(ScanPriority::FoundNote),
186 6 => Ok(ScanPriority::ChainTip),
187 7 => Ok(ScanPriority::Verify),
188 _ => Err(std::io::Error::new(
189 std::io::ErrorKind::InvalidData,
190 "invalid scan priority",
191 )),
192 }?,
193 0 | 1 => match r.read_u8()? {
194 0 => Ok(ScanPriority::Scanning),
195 1 => Ok(ScanPriority::Scanned),
196 2 => Ok(ScanPriority::Historic),
197 3 => Ok(ScanPriority::OpenAdjacent),
198 4 => Ok(ScanPriority::FoundNote),
199 5 => Ok(ScanPriority::ChainTip),
200 6 => Ok(ScanPriority::Verify),
201 _ => Err(std::io::Error::new(
202 std::io::ErrorKind::InvalidData,
203 "invalid scan priority",
204 )),
205 }?,
206 };
207
208 Ok(ScanRange::from_parts(start..end, priority))
209 })?;
210 let sapling_shard_ranges = Vector::read(&mut reader, |r| {
211 let start = BlockHeight::from_u32(r.read_u32::<LittleEndian>()?);
212 let end = BlockHeight::from_u32(r.read_u32::<LittleEndian>()?);
213
214 Ok(start..end)
215 })?;
216 let orchard_shard_ranges = Vector::read(&mut reader, |r| {
217 let start = BlockHeight::from_u32(r.read_u32::<LittleEndian>()?);
218 let end = BlockHeight::from_u32(r.read_u32::<LittleEndian>()?);
219
220 Ok(start..end)
221 })?;
222 let ironwood_shard_ranges = if version >= 4 {
223 Vector::read(&mut reader, |r| {
224 let start = BlockHeight::from_u32(r.read_u32::<LittleEndian>()?);
225 let end = BlockHeight::from_u32(r.read_u32::<LittleEndian>()?);
226
227 Ok(start..end)
228 })?
229 } else {
230 Vec::new()
231 };
232 let scan_targets = Vector::read(&mut reader, |r| {
233 Ok(if version >= 1 {
234 ScanTarget::read(r)?
235 } else {
236 let block_height = BlockHeight::from_u32(r.read_u32::<LittleEndian>()?);
237 let txid = TxId::read(r)?;
238
239 ScanTarget::new(block_height, Some(txid))
240 })
241 })?
242 .into_iter()
243 .collect::<BTreeSet<_>>();
244 let transparent_scan_floor = if version >= 5 {
245 Optional::read(&mut reader, |r| {
246 Ok(BlockHeight::from_u32(r.read_u32::<LittleEndian>()?))
247 })?
248 } else {
249 None
250 };
251
252 Ok(Self {
253 scan_ranges,
254 sapling_shard_ranges,
255 orchard_shard_ranges,
256 ironwood_shard_ranges,
257 scan_targets,
258 initial_sync_state: InitialSyncState::new(),
259 transparent_scan_floor,
260 })
261 }
262
263 pub fn write<W: Write>(&mut self, mut writer: W) -> std::io::Result<()> {
265 writer.write_u8(Self::serialized_version())?;
266 Vector::write(&mut writer, self.scan_ranges(), |w, scan_range| {
267 w.write_u32::<LittleEndian>(scan_range.block_range().start.into())?;
268 w.write_u32::<LittleEndian>(scan_range.block_range().end.into())?;
269 w.write_u8(scan_range.priority() as u8)
270 })?;
271 Vector::write(&mut writer, &self.sapling_shard_ranges, |w, shard_range| {
272 w.write_u32::<LittleEndian>(shard_range.start.into())?;
273 w.write_u32::<LittleEndian>(shard_range.end.into())
274 })?;
275 Vector::write(&mut writer, &self.orchard_shard_ranges, |w, shard_range| {
276 w.write_u32::<LittleEndian>(shard_range.start.into())?;
277 w.write_u32::<LittleEndian>(shard_range.end.into())
278 })?;
279 Vector::write(
280 &mut writer,
281 &self.ironwood_shard_ranges,
282 |w, shard_range| {
283 w.write_u32::<LittleEndian>(shard_range.start.into())?;
284 w.write_u32::<LittleEndian>(shard_range.end.into())
285 },
286 )?;
287 Vector::write(
288 &mut writer,
289 &self.scan_targets.iter().collect::<Vec<_>>(),
290 |w, &scan_target| scan_target.write(w),
291 )?;
292 Optional::write(&mut writer, self.transparent_scan_floor, |w, floor| {
293 w.write_u32::<LittleEndian>(floor.into())
294 })
295 }
296}
297
298impl TreeBounds {
299 fn serialized_version() -> u8 {
300 1
302 }
303
304 pub fn read<R: Read>(mut reader: R) -> std::io::Result<Self> {
306 let version = read_version(&mut reader, "TreeBounds", Self::serialized_version())?;
307 let sapling_initial_tree_size = reader.read_u32::<LittleEndian>()?;
308 let sapling_final_tree_size = reader.read_u32::<LittleEndian>()?;
309 let orchard_initial_tree_size = reader.read_u32::<LittleEndian>()?;
310 let orchard_final_tree_size = reader.read_u32::<LittleEndian>()?;
311 let (ironwood_initial_tree_size, ironwood_final_tree_size) = if version >= 1 {
312 (
313 reader.read_u32::<LittleEndian>()?,
314 reader.read_u32::<LittleEndian>()?,
315 )
316 } else {
317 (0, 0)
318 };
319
320 Ok(Self {
321 sapling_initial_tree_size,
322 sapling_final_tree_size,
323 orchard_initial_tree_size,
324 orchard_final_tree_size,
325 ironwood_initial_tree_size,
326 ironwood_final_tree_size,
327 })
328 }
329
330 pub fn write<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
332 writer.write_u8(Self::serialized_version())?;
333 writer.write_u32::<LittleEndian>(self.sapling_initial_tree_size)?;
334 writer.write_u32::<LittleEndian>(self.sapling_final_tree_size)?;
335 writer.write_u32::<LittleEndian>(self.orchard_initial_tree_size)?;
336 writer.write_u32::<LittleEndian>(self.orchard_final_tree_size)?;
337 writer.write_u32::<LittleEndian>(self.ironwood_initial_tree_size)?;
338 writer.write_u32::<LittleEndian>(self.ironwood_final_tree_size)
339 }
340}
341
342impl NullifierMap {
343 fn serialized_version() -> u8 {
344 2
346 }
347
348 pub fn read<R: Read>(mut reader: R) -> std::io::Result<Self> {
350 let version = read_version(&mut reader, "NullifierMap", Self::serialized_version())?;
351 let sapling = Vector::read(&mut reader, |r| {
352 let mut nullifier_bytes = [0u8; 32];
353 r.read_exact(&mut nullifier_bytes)?;
354 let nullifier =
355 sapling_crypto::Nullifier::from_slice(&nullifier_bytes).map_err(|e| {
356 std::io::Error::new(
357 std::io::ErrorKind::InvalidData,
358 format!("failed to read nullifier. {e}"),
359 )
360 })?;
361 let scan_target = if version >= 1 {
362 ScanTarget::read(r)?
363 } else {
364 let block_height = BlockHeight::from_u32(r.read_u32::<LittleEndian>()?);
365 let txid = TxId::read(r)?;
366
367 ScanTarget::internal(block_height, txid)
368 };
369
370 Ok((nullifier, scan_target))
371 })?
372 .into_iter()
373 .collect::<BTreeMap<_, _>>();
374
375 let orchard = Vector::read(&mut reader, |r| {
376 let mut nullifier_bytes = [0u8; 32];
377 r.read_exact(&mut nullifier_bytes)?;
378 let nullifier = orchard::note::Nullifier::from_bytes(&nullifier_bytes)
379 .expect("nullifier bytes should be valid");
380 let scan_target = if version >= 1 {
381 ScanTarget::read(r)?
382 } else {
383 let block_height = BlockHeight::from_u32(r.read_u32::<LittleEndian>()?);
384 let txid = TxId::read(r)?;
385
386 ScanTarget::internal(block_height, txid)
387 };
388
389 Ok((nullifier, scan_target))
390 })?
391 .into_iter()
392 .collect::<BTreeMap<_, _>>();
393
394 let ironwood = if version >= 2 {
395 Vector::read(&mut reader, |r| {
396 let mut nullifier_bytes = [0u8; 32];
397 r.read_exact(&mut nullifier_bytes)?;
398 let nullifier = orchard::note::Nullifier::from_bytes(&nullifier_bytes)
399 .expect("nullifier bytes should be valid");
400 let scan_target = ScanTarget::read(r)?;
401
402 Ok((nullifier, scan_target))
403 })?
404 .into_iter()
405 .collect::<BTreeMap<_, _>>()
406 } else {
407 BTreeMap::new()
408 };
409
410 Ok(NullifierMap {
411 sapling,
412 orchard,
413 ironwood,
414 })
415 }
416
417 pub fn write<W: Write>(&self, mut writer: W) -> std::io::Result<()> {
419 writer.write_u8(Self::serialized_version())?;
420 Vector::write(
421 &mut writer,
422 &self.sapling.iter().collect::<Vec<_>>(),
423 |w, &(&nullifier, &scan_target)| {
424 w.write_all(nullifier.as_ref())?;
425 scan_target.write(w)
426 },
427 )?;
428 Vector::write(
429 &mut writer,
430 &self.orchard.iter().collect::<Vec<_>>(),
431 |w, &(&nullifier, &scan_target)| {
432 w.write_all(&nullifier.to_bytes())?;
433 scan_target.write(w)
434 },
435 )?;
436 Vector::write(
437 &mut writer,
438 &self.ironwood.iter().collect::<Vec<_>>(),
439 |w, &(&nullifier, &scan_target)| {
440 w.write_all(&nullifier.to_bytes())?;
441 scan_target.write(w)
442 },
443 )
444 }
445}
446
447impl WalletBlock {
448 fn serialized_version() -> u8 {
449 0
450 }
451
452 pub fn read<R: Read>(mut reader: R) -> std::io::Result<Self> {
454 read_version(&mut reader, "WalletBlock", Self::serialized_version())?;
455 let block_height = BlockHeight::from_u32(reader.read_u32::<LittleEndian>()?);
456 let mut block_hash = BlockHash([0u8; 32]);
457 reader.read_exact(&mut block_hash.0)?;
458 let mut prev_hash = BlockHash([0u8; 32]);
459 reader.read_exact(&mut prev_hash.0)?;
460 let time = reader.read_u32::<LittleEndian>()?;
461 let txids = Vector::read(&mut reader, |r| TxId::read(r))?;
462 let tree_bounds = TreeBounds::read(&mut reader)?;
463
464 Ok(Self {
465 block_height,
466 block_hash,
467 prev_hash,
468 time,
469 txids,
470 tree_bounds,
471 })
472 }
473
474 pub fn write<W: Write>(&self, mut writer: W) -> std::io::Result<()> {
476 writer.write_u8(Self::serialized_version())?;
477 writer.write_u32::<LittleEndian>(self.block_height.into())?;
478 writer.write_all(&self.block_hash.0)?;
479 writer.write_all(&self.prev_hash.0)?;
480 writer.write_u32::<LittleEndian>(self.time)?;
481 Vector::write(&mut writer, self.txids(), |w, txid| txid.write(w))?;
482 self.tree_bounds.write(&mut writer)
483 }
484}
485
486impl WalletTransaction {
487 fn serialized_version() -> u8 {
488 1
490 }
491
492 pub fn read<R: Read>(
494 mut reader: R,
495 consensus_parameters: &impl consensus::Parameters,
496 ) -> std::io::Result<Self> {
497 let version = read_version(&mut reader, "WalletTransaction", Self::serialized_version())?;
498 let txid = TxId::read(&mut reader)?;
499 let status = ConfirmationStatus::read(&mut reader)?;
500 let transaction = Transaction::read(
501 &mut reader,
502 consensus::BranchId::for_height(consensus_parameters, status.get_height()),
503 )?;
504 let datetime = reader.read_u32::<LittleEndian>()?;
505 let transparent_coins = Vector::read(&mut reader, |r| TransparentCoin::read(r))?;
506 let sapling_notes = Vector::read(&mut reader, |r| SaplingNote::read(r))?;
507 let orchard_notes = Vector::read(&mut reader, |r| OrchardNote::read(r))?;
508 let outgoing_sapling_notes = Vector::read(&mut reader, |r| {
509 OutgoingSaplingNote::read(r, consensus_parameters)
510 })?;
511 let outgoing_orchard_notes = Vector::read(&mut reader, |r| {
512 OutgoingOrchardNote::read(r, consensus_parameters)
513 })?;
514 let (ironwood_notes, outgoing_ironwood_notes) = if version >= 1 {
515 (
516 Vector::read(&mut reader, |r| IronwoodNote::read(r))?,
517 Vector::read(&mut reader, |r| {
518 OutgoingIronwoodNote::read(r, consensus_parameters)
519 })?,
520 )
521 } else {
522 (Vec::new(), Vec::new())
523 };
524
525 Ok(Self {
526 txid,
527 status,
528 transaction,
529 datetime,
530 transparent_coins,
531 sapling_notes,
532 orchard_notes,
533 ironwood_notes,
534 outgoing_sapling_notes,
535 outgoing_orchard_notes,
536 outgoing_ironwood_notes,
537 })
538 }
539
540 pub fn write<W: Write>(
542 &self,
543 mut writer: W,
544 consensus_parameters: &impl consensus::Parameters,
545 ) -> std::io::Result<()> {
546 writer.write_u8(Self::serialized_version())?;
547 self.txid.write(&mut writer)?;
548 self.status.write(&mut writer)?;
549 self.transaction.write(&mut writer)?;
550 writer.write_u32::<LittleEndian>(self.datetime)?;
551 Vector::write(&mut writer, self.transparent_coins(), |w, output| {
552 output.write(w)
553 })?;
554 Vector::write(&mut writer, self.sapling_notes(), |w, output| {
555 output.write(w)
556 })?;
557 Vector::write(&mut writer, self.orchard_notes(), |w, output| {
558 output.write(w)
559 })?;
560 Vector::write(&mut writer, self.outgoing_sapling_notes(), |w, output| {
561 output.write(w, consensus_parameters)
562 })?;
563 Vector::write(&mut writer, self.outgoing_orchard_notes(), |w, output| {
564 output.write(w, consensus_parameters)
565 })?;
566 Vector::write(&mut writer, self.ironwood_notes(), |w, output| {
567 output.write(w)
568 })?;
569 Vector::write(&mut writer, self.outgoing_ironwood_notes(), |w, output| {
570 output.write(w, consensus_parameters)
571 })
572 }
573}
574
575impl TransparentCoin {
576 fn serialized_version() -> u8 {
577 1
578 }
579
580 pub fn read<R: Read>(mut reader: R) -> std::io::Result<Self> {
582 let version = read_version(&mut reader, "TransparentCoin", Self::serialized_version())?;
583
584 let txid = TxId::read(&mut reader)?;
585 let output_index = if version >= 1 {
586 reader.read_u32::<LittleEndian>()?
587 } else {
588 u32::from(reader.read_u16::<LittleEndian>()?)
589 };
590
591 let account_id = zip32::AccountId::try_from(reader.read_u32::<LittleEndian>()?)
592 .expect("only valid account ids written");
593 let scope = TransparentScope::try_from(reader.read_u8()?)?;
594 let address_index = reader.read_u32::<LittleEndian>()?;
595
596 let address = read_string(&mut reader)?;
597 let script = Script::read(&mut reader)?;
598 let value = Zatoshis::from_u64(reader.read_u64::<LittleEndian>()?)
599 .expect("only valid values written");
600 let spending_transaction = Optional::read(&mut reader, TxId::read)?;
601
602 Ok(Self {
603 output_id: OutputId { txid, output_index },
604 key_id: TransparentAddressId::new(
605 account_id,
606 scope,
607 NonHardenedChildIndex::from_index(address_index)
608 .expect("only non-hardened child indexes should be written"),
609 ),
610 address,
611 value,
612 script,
613 spending_transaction,
614 })
615 }
616
617 pub fn write<W: Write>(&self, mut writer: W) -> std::io::Result<()> {
619 writer.write_u8(Self::serialized_version())?;
620
621 self.output_id.txid().write(&mut writer)?;
622 writer.write_u32::<LittleEndian>(self.output_id.output_index())?;
623
624 writer.write_u32::<LittleEndian>(self.key_id.account_id().into())?;
625 writer.write_u8(self.key_id.scope() as u8)?;
626 writer.write_u32::<LittleEndian>(self.key_id.address_index().index())?;
627
628 write_string(&mut writer, &self.address)?;
629 self.script.write(&mut writer)?;
630 writer.write_u64::<LittleEndian>(self.value())?;
631 Optional::write(&mut writer, self.spending_transaction, |w, txid| {
632 txid.write(w)
633 })?;
634
635 Ok(())
636 }
637}
638
639impl<N, Nf: Copy, P> WalletNote<N, Nf, P> {
640 fn serialized_version() -> u8 {
641 2
642 }
643}
644
645fn read_refetch_nullifier_ranges(
646 reader: &mut impl Read,
647 version: u8,
648) -> std::io::Result<Vec<Range<BlockHeight>>> {
649 if version >= 1 {
650 Vector::read(reader, |r| {
651 let start = r.read_u32::<LittleEndian>()?;
652 let end = r.read_u32::<LittleEndian>()?;
653 Ok(BlockHeight::from_u32(start)..BlockHeight::from_u32(end))
654 })
655 } else {
656 Ok(Vec::new())
657 }
658}
659
660fn write_refetch_nullifier_ranges(
661 writer: &mut impl Write,
662 ranges: &[Range<BlockHeight>],
663) -> std::io::Result<()> {
664 Vector::write(writer, ranges, |w, range| {
665 w.write_u32::<LittleEndian>(range.start.into())?;
666 w.write_u32::<LittleEndian>(range.end.into())
667 })
668}
669
670impl SaplingNote {
671 pub fn read<R: Read>(mut reader: R) -> std::io::Result<Self> {
673 let version = read_version(&mut reader, "WalletNote", Self::serialized_version())?;
674
675 let txid = TxId::read(&mut reader)?;
676 let output_index = if version >= 2 {
677 reader.read_u32::<LittleEndian>()?
678 } else {
679 u32::from(reader.read_u16::<LittleEndian>()?)
680 };
681
682 let account_id =
683 zip32::AccountId::try_from(reader.read_u32::<LittleEndian>()?).map_err(|e| {
684 std::io::Error::new(
685 std::io::ErrorKind::InvalidData,
686 format!("failed to read account id. {e}"),
687 )
688 })?;
689 let scope = match reader.read_u8()? {
690 0 => Ok(zip32::Scope::External),
691 1 => Ok(zip32::Scope::Internal),
692 _ => Err(std::io::Error::new(
693 std::io::ErrorKind::InvalidData,
694 "invalid scope value",
695 )),
696 }?;
697
698 let mut address_bytes = [0u8; 43];
699 reader.read_exact(&mut address_bytes)?;
700 let recipient =
701 sapling_crypto::PaymentAddress::from_bytes(&address_bytes).ok_or_else(|| {
702 std::io::Error::new(
703 std::io::ErrorKind::InvalidData,
704 "failed to read payment address",
705 )
706 })?;
707 let value = sapling_crypto::value::NoteValue::from_raw(reader.read_u64::<LittleEndian>()?);
708 let rseed_zip212 = reader.read_u8()?;
709 let mut rseed_bytes = [0u8; 32];
710 reader.read_exact(&mut rseed_bytes)?;
711 let rseed = match rseed_zip212 {
712 0 => sapling_crypto::Rseed::BeforeZip212(
713 jubjub::Fr::from_bytes(&rseed_bytes).expect("should read valid jubjub bytes"),
714 ),
715 1 => sapling_crypto::Rseed::AfterZip212(rseed_bytes),
716 _ => {
717 return Err(std::io::Error::new(
718 std::io::ErrorKind::InvalidData,
719 "invalid rseed zip212 byte",
720 ));
721 }
722 };
723
724 let nullifier = Optional::read(&mut reader, |r| {
725 let mut nullifier_bytes = [0u8; 32];
726 r.read_exact(&mut nullifier_bytes)?;
727
728 sapling_crypto::Nullifier::from_slice(&nullifier_bytes).map_err(|e| {
729 std::io::Error::new(
730 std::io::ErrorKind::InvalidData,
731 format!("failed to read nullifier. {e}"),
732 )
733 })
734 })?;
735 let position = Optional::read(&mut reader, |r| {
736 Ok(Position::from(r.read_u64::<LittleEndian>()?))
737 })?;
738 let mut memo_bytes = [0u8; 512];
739 reader.read_exact(&mut memo_bytes)?;
740 let memo = Memo::from_bytes(&memo_bytes).map_err(|e| {
741 std::io::Error::new(
742 std::io::ErrorKind::InvalidData,
743 format!("failed to read memo. {e}"),
744 )
745 })?;
746
747 let spending_transaction = Optional::read(&mut reader, TxId::read)?;
748 let refetch_nullifier_ranges = read_refetch_nullifier_ranges(&mut reader, version)?;
749
750 Ok(Self {
751 output_id: OutputId::new(txid, output_index),
752 key_id: KeyId::from_parts(account_id, scope),
753 note: sapling_crypto::Note::from_parts(recipient, value, rseed),
754 nullifier,
755 position,
756 memo,
757 spending_transaction,
758 refetch_nullifier_ranges,
759 marker: std::marker::PhantomData,
760 })
761 }
762
763 pub fn write<W: Write>(&self, mut writer: W) -> std::io::Result<()> {
765 writer.write_u8(Self::serialized_version())?;
766
767 self.output_id.txid().write(&mut writer)?;
768 writer.write_u32::<LittleEndian>(self.output_id.output_index())?;
769
770 writer.write_u32::<LittleEndian>(self.key_id.account_id.into())?;
771 writer.write_u8(self.key_id.scope as u8)?;
772
773 writer.write_all(&self.note.recipient().to_bytes())?;
774 writer.write_u64::<LittleEndian>(self.value())?;
775 match self.note.rseed() {
776 sapling_crypto::Rseed::BeforeZip212(fr) => {
777 writer.write_u8(0)?;
778 writer.write_all(&fr.to_bytes())?;
779 }
780 sapling_crypto::Rseed::AfterZip212(bytes) => {
781 writer.write_u8(1)?;
782 writer.write_all(bytes)?;
783 }
784 }
785
786 Optional::write(&mut writer, self.nullifier, |w, nullifier| {
787 w.write_all(nullifier.as_ref())
788 })?;
789 Optional::write(&mut writer, self.position, |w, position| {
790 w.write_u64::<LittleEndian>(position.into())
791 })?;
792 writer.write_all(self.memo.encode().as_array())?;
793
794 Optional::write(&mut writer, self.spending_transaction, |w, txid| {
795 txid.write(w)
796 })?;
797
798 write_refetch_nullifier_ranges(&mut writer, &self.refetch_nullifier_ranges)
799 }
800}
801
802fn read_orchard_protocol_note<R: Read, P>(
806 mut reader: R,
807 note_version: orchard::note::NoteVersion,
808) -> std::io::Result<WalletNote<orchard::Note, orchard::note::Nullifier, P>> {
809 let version = read_version(
810 &mut reader,
811 "WalletNote",
812 WalletNote::<orchard::Note, orchard::note::Nullifier, P>::serialized_version(),
813 )?;
814
815 let txid = TxId::read(&mut reader)?;
816 let output_index = if version >= 2 {
817 reader.read_u32::<LittleEndian>()?
818 } else {
819 u32::from(reader.read_u16::<LittleEndian>()?)
820 };
821
822 let account_id =
823 zip32::AccountId::try_from(reader.read_u32::<LittleEndian>()?).map_err(|e| {
824 std::io::Error::new(
825 std::io::ErrorKind::InvalidData,
826 format!("failed to read account id. {e}"),
827 )
828 })?;
829 let scope = match reader.read_u8()? {
830 0 => Ok(zip32::Scope::External),
831 1 => Ok(zip32::Scope::Internal),
832 _ => Err(std::io::Error::new(
833 std::io::ErrorKind::InvalidData,
834 "invalid scope value",
835 )),
836 }?;
837
838 let mut address_bytes = [0u8; 43];
839 reader.read_exact(&mut address_bytes)?;
840 let recipient = orchard::Address::from_raw_address_bytes(&address_bytes)
841 .expect("should be a valid address");
842 let value = orchard::value::NoteValue::from_raw(reader.read_u64::<LittleEndian>()?);
843 let mut rho_bytes = [0u8; 32];
844 reader.read_exact(&mut rho_bytes)?;
845 let rho = orchard::note::Rho::from_bytes(&rho_bytes).expect("should be valid rho bytes");
846 let mut rseed_bytes = [0u8; 32];
847 reader.read_exact(&mut rseed_bytes)?;
848 let rseed = orchard::note::RandomSeed::from_bytes(rseed_bytes, &rho)
849 .expect("should be valid random seed bytes");
850
851 let nullifier = Optional::read(&mut reader, |r| {
852 let mut nullifier_bytes = [0u8; 32];
853 r.read_exact(&mut nullifier_bytes)?;
854
855 Ok(orchard::note::Nullifier::from_bytes(&nullifier_bytes)
856 .expect("should be valid nullfiier bytes"))
857 })?;
858 let position = Optional::read(&mut reader, |r| {
859 Ok(Position::from(r.read_u64::<LittleEndian>()?))
860 })?;
861 let mut memo_bytes = [0u8; 512];
862 reader.read_exact(&mut memo_bytes)?;
863 let memo = Memo::from_bytes(&memo_bytes).map_err(|e| {
864 std::io::Error::new(
865 std::io::ErrorKind::InvalidData,
866 format!("failed to read memo. {e}"),
867 )
868 })?;
869
870 let spending_transaction = Optional::read(&mut reader, TxId::read)?;
871 let refetch_nullifier_ranges = read_refetch_nullifier_ranges(&mut reader, version)?;
872
873 Ok(WalletNote {
874 output_id: OutputId::new(txid, output_index),
875 key_id: KeyId::from_parts(account_id, scope),
876 note: orchard::note::Note::from_parts(recipient, value, rho, rseed, note_version)
877 .expect("should be a valid orchard note"),
878 nullifier,
879 position,
880 memo,
881 spending_transaction,
882 refetch_nullifier_ranges,
883 marker: std::marker::PhantomData,
884 })
885}
886
887fn write_orchard_protocol_note<W: Write, P>(
889 note: &WalletNote<orchard::Note, orchard::note::Nullifier, P>,
890 mut writer: W,
891) -> std::io::Result<()> {
892 writer
893 .write_u8(WalletNote::<orchard::Note, orchard::note::Nullifier, P>::serialized_version())?;
894
895 note.output_id.txid().write(&mut writer)?;
896 writer.write_u32::<LittleEndian>(note.output_id.output_index())?;
897
898 writer.write_u32::<LittleEndian>(note.key_id.account_id.into())?;
899 writer.write_u8(note.key_id.scope as u8)?;
900
901 writer.write_all(¬e.note.recipient().to_raw_address_bytes())?;
902 writer.write_u64::<LittleEndian>(note.note.value().inner())?;
903 writer.write_all(¬e.note.rho().to_bytes())?;
904 writer.write_all(note.note.rseed().as_bytes())?;
905
906 Optional::write(&mut writer, note.nullifier, |w, nullifier| {
907 w.write_all(&nullifier.to_bytes())
908 })?;
909 Optional::write(&mut writer, note.position, |w, position| {
910 w.write_u64::<LittleEndian>(position.into())
911 })?;
912 writer.write_all(note.memo.encode().as_array())?;
913 Optional::write(&mut writer, note.spending_transaction, |w, txid| {
914 txid.write(w)
915 })?;
916
917 write_refetch_nullifier_ranges(&mut writer, ¬e.refetch_nullifier_ranges)
918}
919
920impl OrchardNote {
921 pub fn read<R: Read>(reader: R) -> std::io::Result<Self> {
923 read_orchard_protocol_note(reader, orchard::note::NoteVersion::V2)
924 }
925
926 pub fn write<W: Write>(&self, writer: W) -> std::io::Result<()> {
928 write_orchard_protocol_note(self, writer)
929 }
930}
931
932impl IronwoodNote {
933 pub fn read<R: Read>(reader: R) -> std::io::Result<Self> {
935 read_orchard_protocol_note(reader, orchard::note::NoteVersion::V3)
936 }
937
938 pub fn write<W: Write>(&self, writer: W) -> std::io::Result<()> {
940 write_orchard_protocol_note(self, writer)
941 }
942}
943
944impl<N, P> OutgoingNote<N, P> {
945 fn serialized_version() -> u8 {
946 1
947 }
948}
949
950impl OutgoingSaplingNote {
951 pub fn read<R: Read>(
953 mut reader: R,
954 consensus_parameters: &impl consensus::Parameters,
955 ) -> std::io::Result<Self> {
956 let version = read_version(&mut reader, "OutgoingNote", Self::serialized_version())?;
957
958 let txid = TxId::read(&mut reader)?;
959 let output_index = if version >= 1 {
960 reader.read_u32::<LittleEndian>()?
961 } else {
962 u32::from(reader.read_u16::<LittleEndian>()?)
963 };
964
965 let account_id =
966 zip32::AccountId::try_from(reader.read_u32::<LittleEndian>()?).map_err(|e| {
967 std::io::Error::new(
968 std::io::ErrorKind::InvalidData,
969 format!("failed to read account id. {e}"),
970 )
971 })?;
972 let scope = match reader.read_u8()? {
973 0 => Ok(zip32::Scope::External),
974 1 => Ok(zip32::Scope::Internal),
975 _ => Err(std::io::Error::new(
976 std::io::ErrorKind::InvalidData,
977 "invalid scope value",
978 )),
979 }?;
980
981 let mut address_bytes = [0u8; 43];
982 reader.read_exact(&mut address_bytes)?;
983 let recipient =
984 sapling_crypto::PaymentAddress::from_bytes(&address_bytes).ok_or_else(|| {
985 std::io::Error::new(
986 std::io::ErrorKind::InvalidData,
987 "failed to read payment address",
988 )
989 })?;
990 let value = sapling_crypto::value::NoteValue::from_raw(reader.read_u64::<LittleEndian>()?);
991 let rseed_zip212 = reader.read_u8()?;
992 let mut rseed_bytes = [0u8; 32];
993 reader.read_exact(&mut rseed_bytes)?;
994 let rseed = match rseed_zip212 {
995 0 => sapling_crypto::Rseed::BeforeZip212(
996 jubjub::Fr::from_bytes(&rseed_bytes).expect("should read valid jubjub bytes"),
997 ),
998 1 => sapling_crypto::Rseed::AfterZip212(rseed_bytes),
999 _ => {
1000 return Err(std::io::Error::new(
1001 std::io::ErrorKind::InvalidData,
1002 "invalid rseed zip212 byte",
1003 ));
1004 }
1005 };
1006
1007 let mut memo_bytes = [0u8; 512];
1008 reader.read_exact(&mut memo_bytes)?;
1009 let memo = Memo::from_bytes(&memo_bytes).map_err(|e| {
1010 std::io::Error::new(
1011 std::io::ErrorKind::InvalidData,
1012 format!("failed to read memo. {e}"),
1013 )
1014 })?;
1015
1016 let recipient_unified_address = Optional::read(&mut reader, |r| {
1017 let encoded_address = read_string(r)?;
1018
1019 decode_unified_address(consensus_parameters, &encoded_address)
1020 })?;
1021
1022 Ok(Self {
1023 output_id: OutputId::new(txid, output_index),
1024 key_id: KeyId::from_parts(account_id, scope),
1025 note: sapling_crypto::Note::from_parts(recipient, value, rseed),
1026 memo,
1027 recipient_full_unified_address: recipient_unified_address,
1028 marker: std::marker::PhantomData,
1029 })
1030 }
1031
1032 pub fn write<W: Write>(
1034 &self,
1035 mut writer: W,
1036 consensus_parameters: &impl consensus::Parameters,
1037 ) -> std::io::Result<()> {
1038 writer.write_u8(Self::serialized_version())?;
1039
1040 self.output_id.txid().write(&mut writer)?;
1041 writer.write_u32::<LittleEndian>(self.output_id.output_index())?;
1042
1043 writer.write_u32::<LittleEndian>(self.key_id.account_id.into())?;
1044 writer.write_u8(self.key_id.scope as u8)?;
1045
1046 writer.write_all(&self.note.recipient().to_bytes())?;
1047 writer.write_u64::<LittleEndian>(self.value())?;
1048 match self.note.rseed() {
1049 sapling_crypto::Rseed::BeforeZip212(fr) => {
1050 writer.write_u8(0)?;
1051 writer.write_all(&fr.to_bytes())?;
1052 }
1053 sapling_crypto::Rseed::AfterZip212(bytes) => {
1054 writer.write_u8(1)?;
1055 writer.write_all(bytes)?;
1056 }
1057 }
1058
1059 writer.write_all(self.memo.encode().as_array())?;
1060 Optional::write(
1061 &mut writer,
1062 self.recipient_full_unified_address.as_ref(),
1063 |w, unified_address| write_string(w, &unified_address.encode(consensus_parameters)),
1064 )?;
1065
1066 Ok(())
1067 }
1068}
1069
1070fn read_orchard_protocol_outgoing_note<R: Read, P>(
1074 mut reader: R,
1075 consensus_parameters: &impl consensus::Parameters,
1076 note_version: orchard::note::NoteVersion,
1077) -> std::io::Result<OutgoingNote<orchard::Note, P>> {
1078 let version = read_version(
1079 &mut reader,
1080 "OutgoingNote",
1081 OutgoingNote::<orchard::Note, P>::serialized_version(),
1082 )?;
1083
1084 let txid = TxId::read(&mut reader)?;
1085 let output_index = if version >= 1 {
1086 reader.read_u32::<LittleEndian>()?
1087 } else {
1088 u32::from(reader.read_u16::<LittleEndian>()?)
1089 };
1090
1091 let account_id =
1092 zip32::AccountId::try_from(reader.read_u32::<LittleEndian>()?).map_err(|e| {
1093 std::io::Error::new(
1094 std::io::ErrorKind::InvalidData,
1095 format!("failed to read account id. {e}"),
1096 )
1097 })?;
1098 let scope = match reader.read_u8()? {
1099 0 => Ok(zip32::Scope::External),
1100 1 => Ok(zip32::Scope::Internal),
1101 _ => Err(std::io::Error::new(
1102 std::io::ErrorKind::InvalidData,
1103 "invalid scope value",
1104 )),
1105 }?;
1106
1107 let mut address_bytes = [0u8; 43];
1108 reader.read_exact(&mut address_bytes)?;
1109 let recipient = orchard::Address::from_raw_address_bytes(&address_bytes)
1110 .expect("should be a valid address");
1111 let value = orchard::value::NoteValue::from_raw(reader.read_u64::<LittleEndian>()?);
1112 let mut rho_bytes = [0u8; 32];
1113 reader.read_exact(&mut rho_bytes)?;
1114 let rho = orchard::note::Rho::from_bytes(&rho_bytes).expect("should be valid rho bytes");
1115 let mut rseed_bytes = [0u8; 32];
1116 reader.read_exact(&mut rseed_bytes)?;
1117 let rseed = orchard::note::RandomSeed::from_bytes(rseed_bytes, &rho)
1118 .expect("should be valid random seed bytes");
1119
1120 let mut memo_bytes = [0u8; 512];
1121 reader.read_exact(&mut memo_bytes)?;
1122 let memo = Memo::from_bytes(&memo_bytes).map_err(|e| {
1123 std::io::Error::new(
1124 std::io::ErrorKind::InvalidData,
1125 format!("failed to read memo. {e}"),
1126 )
1127 })?;
1128
1129 let recipient_unified_address = Optional::read(&mut reader, |r| {
1130 let encoded_address = read_string(r)?;
1131
1132 decode_unified_address(consensus_parameters, &encoded_address)
1133 })?;
1134
1135 Ok(OutgoingNote {
1136 output_id: OutputId::new(txid, output_index),
1137 key_id: KeyId::from_parts(account_id, scope),
1138 note: orchard::note::Note::from_parts(recipient, value, rho, rseed, note_version)
1139 .expect("should be a valid orchard note"),
1140 memo,
1141 recipient_full_unified_address: recipient_unified_address,
1142 marker: std::marker::PhantomData,
1143 })
1144}
1145
1146fn write_orchard_protocol_outgoing_note<W: Write, P>(
1148 note: &OutgoingNote<orchard::Note, P>,
1149 mut writer: W,
1150 consensus_parameters: &impl consensus::Parameters,
1151) -> std::io::Result<()> {
1152 writer.write_u8(OutgoingNote::<orchard::Note, P>::serialized_version())?;
1153
1154 note.output_id.txid().write(&mut writer)?;
1155 writer.write_u32::<LittleEndian>(note.output_id.output_index())?;
1156
1157 writer.write_u32::<LittleEndian>(note.key_id.account_id.into())?;
1158 writer.write_u8(note.key_id.scope as u8)?;
1159
1160 writer.write_all(¬e.note.recipient().to_raw_address_bytes())?;
1161 writer.write_u64::<LittleEndian>(note.note.value().inner())?;
1162 writer.write_all(¬e.note.rho().to_bytes())?;
1163 writer.write_all(note.note.rseed().as_bytes())?;
1164
1165 writer.write_all(note.memo.encode().as_array())?;
1166 Optional::write(
1167 &mut writer,
1168 note.recipient_full_unified_address.as_ref(),
1169 |w, unified_address| write_string(w, &unified_address.encode(consensus_parameters)),
1170 )?;
1171
1172 Ok(())
1173}
1174
1175impl OutgoingOrchardNote {
1176 pub fn read<R: Read>(
1178 reader: R,
1179 consensus_parameters: &impl consensus::Parameters,
1180 ) -> std::io::Result<Self> {
1181 read_orchard_protocol_outgoing_note(
1182 reader,
1183 consensus_parameters,
1184 orchard::note::NoteVersion::V2,
1185 )
1186 }
1187
1188 pub fn write<W: Write>(
1190 &self,
1191 writer: W,
1192 consensus_parameters: &impl consensus::Parameters,
1193 ) -> std::io::Result<()> {
1194 write_orchard_protocol_outgoing_note(self, writer, consensus_parameters)
1195 }
1196}
1197
1198impl OutgoingIronwoodNote {
1199 pub fn read<R: Read>(
1201 reader: R,
1202 consensus_parameters: &impl consensus::Parameters,
1203 ) -> std::io::Result<Self> {
1204 read_orchard_protocol_outgoing_note(
1205 reader,
1206 consensus_parameters,
1207 orchard::note::NoteVersion::V3,
1208 )
1209 }
1210
1211 pub fn write<W: Write>(
1213 &self,
1214 writer: W,
1215 consensus_parameters: &impl consensus::Parameters,
1216 ) -> std::io::Result<()> {
1217 write_orchard_protocol_outgoing_note(self, writer, consensus_parameters)
1218 }
1219}
1220
1221impl ShardTrees {
1222 fn serialized_version() -> u8 {
1223 1
1225 }
1226
1227 pub fn read<R: Read>(mut reader: R) -> std::io::Result<Self> {
1229 let version = read_version(&mut reader, "ShardTrees", Self::serialized_version())?;
1230 let sapling = Self::read_shardtree(&mut reader)?;
1231 let orchard = Self::read_shardtree(&mut reader)?;
1232 let ironwood = if version >= 1 {
1233 Self::read_shardtree(&mut reader)?
1234 } else {
1235 Self::new().ironwood
1237 };
1238
1239 Ok(Self {
1240 sapling,
1241 orchard,
1242 ironwood,
1243 })
1244 }
1245
1246 pub fn write<W: Write>(&mut self, mut writer: W) -> std::io::Result<()> {
1248 writer.write_u8(Self::serialized_version())?;
1249 Self::write_shardtree(&mut writer, &mut self.sapling)?;
1250 Self::write_shardtree(&mut writer, &mut self.orchard)?;
1251 Self::write_shardtree(&mut writer, &mut self.ironwood)?;
1252
1253 Ok(())
1254 }
1255
1256 fn read_shardtree<
1257 H: Hashable + Clone + HashSer + Eq,
1258 C: Ord + std::fmt::Debug + Copy + From<u32>,
1259 R: Read,
1260 const DEPTH: u8,
1261 const SHARD_HEIGHT: u8,
1262 >(
1263 mut reader: R,
1264 ) -> std::io::Result<ShardTree<MemoryShardStore<H, C>, DEPTH, SHARD_HEIGHT>> {
1265 let shards = Vector::read(&mut reader, |r| {
1266 let level = incrementalmerkletree::Level::from(r.read_u8()?);
1267 let index = r.read_u64::<LittleEndian>()?;
1268 let root_addr = incrementalmerkletree::Address::from_parts(level, index);
1269 let shard = read_shard(r)?;
1270
1271 LocatedPrunableTree::from_parts(root_addr, shard).map_err(|addr| {
1272 std::io::Error::new(
1273 std::io::ErrorKind::InvalidData,
1274 format!("parent node in root has level 0 relative to root address: {addr:?}"),
1275 )
1276 })
1277 })?;
1278 let mut store = MemoryShardStore::empty();
1279 for shard in shards {
1280 store.put_shard(shard).expect("infallible");
1281 }
1282 let checkpoints = Vector::read(&mut reader, |r| {
1283 let checkpoint_id = C::from(r.read_u32::<LittleEndian>()?);
1284 let tree_state = match r.read_u8()? {
1285 0 => TreeState::Empty,
1286 1 => TreeState::AtPosition(Position::from(r.read_u64::<LittleEndian>()?)),
1287 otherwise => {
1288 return Err(std::io::Error::new(
1289 std::io::ErrorKind::InvalidData,
1290 format!(
1291 "failed to read TreeState. expected boolean value, found {otherwise}"
1292 ),
1293 ));
1294 }
1295 };
1296 let marks_removed =
1297 Vector::read(r, |r| r.read_u64::<LittleEndian>().map(Position::from))?;
1298 Ok((
1299 checkpoint_id,
1300 Checkpoint::from_parts(tree_state, marks_removed.into_iter().collect()),
1301 ))
1302 })?;
1303 if checkpoints.is_empty() {
1304 store
1305 .add_checkpoint(C::from(0), Checkpoint::tree_empty())
1306 .expect("Infallible");
1307 }
1308 for (checkpoint_id, checkpoint) in checkpoints {
1309 store
1310 .add_checkpoint(checkpoint_id, checkpoint)
1311 .expect("Infallible");
1312 }
1313 store.put_cap(read_shard(reader)?).expect("Infallible");
1314
1315 Ok(shardtree::ShardTree::new(
1316 store,
1317 SHARDTREE_CHECKPOINT_ROLLING_WINDOW_SIZE as usize,
1318 ))
1319 }
1320
1321 fn write_shardtree<
1323 H: Hashable + Clone + Eq + HashSer,
1324 C: Ord + std::fmt::Debug + Copy,
1325 W: Write,
1326 const DEPTH: u8,
1327 const SHARD_HEIGHT: u8,
1328 >(
1329 mut writer: W,
1330 shardtree: &mut ShardTree<MemoryShardStore<H, C>, DEPTH, SHARD_HEIGHT>,
1331 ) -> std::io::Result<()>
1332 where
1333 u32: From<C>,
1334 {
1335 fn write_shards<W, H, C>(
1336 mut writer: W,
1337 store: &MemoryShardStore<H, C>,
1338 ) -> std::io::Result<()>
1339 where
1340 H: Hashable + Clone + Eq + HashSer,
1341 C: Ord + std::fmt::Debug + Copy,
1342 W: Write,
1343 {
1344 let roots = store.get_shard_roots().expect("Infallible");
1345 Vector::write(&mut writer, &roots, |w, root| {
1346 w.write_u8(root.level().into())?;
1347 w.write_u64::<LittleEndian>(root.index())?;
1348 let shard = store
1349 .get_shard(*root)
1350 .expect("Infallible")
1351 .expect("cannot find root that shard store claims to have");
1352 write_shard(w, shard.root())
1353 })
1354 }
1355
1356 fn write_checkpoints<W, Cid>(
1357 mut writer: W,
1358 checkpoints: &[(Cid, Checkpoint)],
1359 ) -> std::io::Result<()>
1360 where
1361 W: Write,
1362 Cid: Ord + std::fmt::Debug + Copy,
1363 u32: From<Cid>,
1364 {
1365 Vector::write(
1366 &mut writer,
1367 checkpoints,
1368 |mut w, (checkpoint_id, checkpoint)| {
1369 w.write_u32::<LittleEndian>(u32::from(*checkpoint_id))?;
1370 match checkpoint.tree_state() {
1371 shardtree::store::TreeState::Empty => w.write_u8(0),
1372 shardtree::store::TreeState::AtPosition(pos) => {
1373 w.write_u8(1)?;
1374 w.write_u64::<LittleEndian>(<u64 as From<Position>>::from(pos))
1375 }
1376 }?;
1377 Vector::write(
1378 &mut w,
1379 &checkpoint.marks_removed().iter().collect::<Vec<_>>(),
1380 |w, mark| {
1381 w.write_u64::<LittleEndian>(<u64 as From<Position>>::from(**mark))
1382 },
1383 )
1384 },
1385 )
1386 }
1387
1388 let mut store = std::mem::replace(
1390 shardtree,
1391 shardtree::ShardTree::new(MemoryShardStore::empty(), 0),
1392 )
1393 .into_store();
1394
1395 macro_rules! write_with_error_handling {
1396 ($writer: ident, $from: ident) => {
1397 if let Err(e) = $writer(&mut writer, &$from) {
1398 *shardtree = shardtree::ShardTree::new(
1399 store,
1400 SHARDTREE_CHECKPOINT_ROLLING_WINDOW_SIZE as usize,
1401 );
1402 return Err(e);
1403 }
1404 };
1405 }
1406
1407 write_with_error_handling!(write_shards, store);
1409
1410 let mut checkpoints = Vec::new();
1412 let checkpoint_count = store.checkpoint_count().expect("Infallible");
1413 store
1414 .with_checkpoints(checkpoint_count, |checkpoint_id, checkpoint| {
1415 checkpoints.push((*checkpoint_id, checkpoint.clone()));
1416 Ok(())
1417 })
1418 .expect("Infallible");
1419 write_with_error_handling!(write_checkpoints, checkpoints);
1420
1421 let cap = store.get_cap().expect("Infallible");
1423 write_with_error_handling!(write_shard, cap);
1424
1425 *shardtree =
1426 shardtree::ShardTree::new(store, SHARDTREE_CHECKPOINT_ROLLING_WINDOW_SIZE as usize);
1427
1428 Ok(())
1429 }
1430}
1431
1432#[cfg(test)]
1433mod tests {
1434 use crate::{sync::MAX_SHARDTREE_CHECKPOINTS, witness::ANCHOR_RETENTION_INTERVALS};
1435
1436 use super::*;
1437
1438 const APPENDED: u32 = SHARDTREE_CHECKPOINT_ROLLING_WINDOW_SIZE + 49;
1440
1441 fn written(scan_target: &ScanTarget) -> Vec<u8> {
1442 let mut bytes = Vec::new();
1443 scan_target.write(&mut bytes).unwrap();
1444 bytes
1445 }
1446
1447 #[test]
1448 fn scan_target_round_trips_with_its_pools_and_origin() {
1449 let txid = TxId::from_bytes([3; 32]);
1450 for scan_target in [
1451 ScanTarget::new(BlockHeight::from_u32(10), None),
1452 ScanTarget::new(BlockHeight::from_u32(10), Some(txid)),
1453 ScanTarget::internal(BlockHeight::from_u32(20), txid)
1454 .with_pool(zcash_protocol::ShieldedPool::Sapling)
1455 .with_pool(zcash_protocol::ShieldedPool::Ironwood),
1456 ] {
1457 let read = ScanTarget::read(written(&scan_target).as_slice()).unwrap();
1458 assert_eq!(read, scan_target);
1459 assert_eq!(read.txid(), scan_target.txid());
1460 assert_eq!(read.pools(), scan_target.pools());
1461 assert_eq!(read.origin(), scan_target.origin());
1462 }
1463 }
1464
1465 #[test]
1468 fn scan_target_version_zero_is_read_as_external() {
1469 let v0 = |txid: [u8; 32]| {
1470 let mut bytes = vec![0u8];
1471 bytes.write_u32::<LittleEndian>(30).unwrap();
1472 bytes.extend_from_slice(&txid);
1473 bytes.push(1);
1474 bytes
1475 };
1476
1477 let without_transaction = ScanTarget::read(v0([0; 32]).as_slice()).unwrap();
1478 assert_eq!(
1479 without_transaction,
1480 ScanTarget::new(BlockHeight::from_u32(30), None)
1481 );
1482 assert_eq!(without_transaction.txid(), None);
1483 assert_eq!(without_transaction.origin(), ScanTargetOrigin::External);
1484 assert!(without_transaction.pools().is_empty());
1485
1486 let with_transaction = ScanTarget::read(v0([5; 32]).as_slice()).unwrap();
1487 assert_eq!(with_transaction.txid(), Some(TxId::from_bytes([5; 32])));
1488 }
1489
1490 #[test]
1491 fn scan_target_with_an_unknown_pool_or_origin_is_refused() {
1492 let mut unknown_pool = written(&ScanTarget::new(BlockHeight::from_u32(10), None));
1493 *unknown_pool.last_mut().unwrap() = 0;
1494 let pools_index = unknown_pool.len() - 2;
1495 unknown_pool[pools_index] = 1 << 3;
1496 assert_eq!(
1497 ScanTarget::read(unknown_pool.as_slice())
1498 .unwrap_err()
1499 .kind(),
1500 std::io::ErrorKind::InvalidData
1501 );
1502
1503 let mut unknown_origin = written(&ScanTarget::new(BlockHeight::from_u32(10), None));
1504 *unknown_origin.last_mut().unwrap() = 2;
1505 assert_eq!(
1506 ScanTarget::read(unknown_origin.as_slice())
1507 .unwrap_err()
1508 .kind(),
1509 std::io::ErrorKind::InvalidData
1510 );
1511 }
1512
1513 fn v3_sync_state_bytes() -> Vec<u8> {
1517 let mut out = Vec::new();
1518 out.write_u8(3).unwrap();
1519 Vector::write(&mut out, &[] as &[()], |_, _| Ok(())).unwrap();
1520 Vector::write(&mut out, &[] as &[()], |_, _| Ok(())).unwrap();
1521 Vector::write(&mut out, &[] as &[()], |_, _| Ok(())).unwrap();
1522 Vector::write(&mut out, &[] as &[()], |_, _| Ok(())).unwrap();
1523 out
1524 }
1525
1526 #[test]
1527 fn sync_state_v3_reads_with_empty_ironwood_ranges() {
1528 let bytes = v3_sync_state_bytes();
1529 let sync_state = SyncState::read(bytes.as_slice()).expect("v3 should read cleanly");
1530 assert!(sync_state.ironwood_shard_ranges().is_empty());
1531 }
1532
1533 #[test]
1534 fn sync_state_v4_roundtrip_preserves_ironwood_ranges() {
1535 let mut state = SyncState::new();
1536 state.ironwood_shard_ranges = vec![
1537 BlockHeight::from_u32(100)..BlockHeight::from_u32(200),
1538 BlockHeight::from_u32(300)..BlockHeight::from_u32(400),
1539 ];
1540 state.scan_ranges.push(ScanRange::from_parts(
1541 BlockHeight::from_u32(100)..BlockHeight::from_u32(400),
1542 ScanPriority::Historic,
1543 ));
1544 let mut bytes = Vec::new();
1545 state.write(&mut bytes).expect("write should succeed");
1546 let recovered = SyncState::read(bytes.as_slice()).expect("read should succeed");
1547 assert_eq!(recovered.ironwood_shard_ranges, state.ironwood_shard_ranges);
1548 assert_eq!(recovered.scan_ranges, state.scan_ranges);
1549 }
1550
1551 fn v4_sync_state_bytes() -> Vec<u8> {
1555 let mut out = Vec::new();
1556 out.write_u8(4).unwrap();
1557 Vector::write(&mut out, &[] as &[()], |_, _| Ok(())).unwrap();
1558 Vector::write(&mut out, &[] as &[()], |_, _| Ok(())).unwrap();
1559 Vector::write(&mut out, &[] as &[()], |_, _| Ok(())).unwrap();
1560 Vector::write(&mut out, &[] as &[()], |_, _| Ok(())).unwrap();
1561 Vector::write(&mut out, &[] as &[()], |_, _| Ok(())).unwrap();
1562 out
1563 }
1564
1565 #[test]
1566 fn sync_state_v4_reads_with_unset_transparent_scan_floor() {
1567 let bytes = v4_sync_state_bytes();
1568 let sync_state = SyncState::read(bytes.as_slice()).expect("v4 should read cleanly");
1569 assert_eq!(sync_state.transparent_scan_floor, None);
1570 }
1571
1572 #[test]
1573 fn sync_state_v5_roundtrip_preserves_transparent_scan_floor() {
1574 for transparent_scan_floor in [None, Some(BlockHeight::from_u32(100))] {
1575 let mut state = SyncState::new();
1576 state.transparent_scan_floor = transparent_scan_floor;
1577 let mut bytes = Vec::new();
1578 state.write(&mut bytes).expect("write should succeed");
1579 let recovered = SyncState::read(bytes.as_slice()).expect("read should succeed");
1580 assert_eq!(recovered.transparent_scan_floor, transparent_scan_floor);
1581 }
1582 }
1583
1584 mod read_version {
1585 use super::super::read_version;
1586
1587 const ARBITRARY_CURRENT_VERSION: u8 = 2;
1589
1590 #[test]
1591 fn accepts_versions_up_to_the_current_version() {
1592 for version in [0, ARBITRARY_CURRENT_VERSION] {
1593 assert_eq!(
1594 read_version([version].as_slice(), "Type", ARBITRARY_CURRENT_VERSION).unwrap(),
1595 version
1596 );
1597 }
1598 }
1599
1600 #[test]
1601 fn refuses_a_version_above_the_current_version() {
1602 let error = read_version(
1603 [ARBITRARY_CURRENT_VERSION + 1].as_slice(),
1604 "Type",
1605 ARBITRARY_CURRENT_VERSION,
1606 )
1607 .expect_err("a version above the current version is refused");
1608 assert_eq!(error.kind(), std::io::ErrorKind::InvalidData);
1609 }
1610 }
1611
1612 #[test]
1615 fn readers_refuse_serialized_versions_above_their_own() {
1616 let consensus_parameters = zcash_protocol::consensus::MAIN_NETWORK;
1617 let newer_scan_target = [ScanTarget::serialized_version() + 1];
1618 let newer_sync_state = [SyncState::serialized_version() + 1];
1619 let newer_tree_bounds = [TreeBounds::serialized_version() + 1];
1620 let newer_nullifier_map = [NullifierMap::serialized_version() + 1];
1621 let newer_wallet_block = [WalletBlock::serialized_version() + 1];
1622 let newer_wallet_transaction = [WalletTransaction::serialized_version() + 1];
1623 let newer_transparent_coin = [TransparentCoin::serialized_version() + 1];
1624 let newer_wallet_note = [SaplingNote::serialized_version() + 1];
1625 let newer_outgoing_note = [OutgoingSaplingNote::serialized_version() + 1];
1626 let newer_shard_trees = [ShardTrees::serialized_version() + 1];
1627
1628 for (type_name, read) in [
1629 (
1630 "ScanTarget",
1631 ScanTarget::read(newer_scan_target.as_slice()).map(drop),
1632 ),
1633 (
1634 "SyncState",
1635 SyncState::read(newer_sync_state.as_slice()).map(drop),
1636 ),
1637 (
1638 "TreeBounds",
1639 TreeBounds::read(newer_tree_bounds.as_slice()).map(drop),
1640 ),
1641 (
1642 "NullifierMap",
1643 NullifierMap::read(newer_nullifier_map.as_slice()).map(drop),
1644 ),
1645 (
1646 "WalletBlock",
1647 WalletBlock::read(newer_wallet_block.as_slice()).map(drop),
1648 ),
1649 (
1650 "WalletTransaction",
1651 WalletTransaction::read(newer_wallet_transaction.as_slice(), &consensus_parameters)
1652 .map(drop),
1653 ),
1654 (
1655 "TransparentCoin",
1656 TransparentCoin::read(newer_transparent_coin.as_slice()).map(drop),
1657 ),
1658 (
1659 "SaplingNote",
1660 SaplingNote::read(newer_wallet_note.as_slice()).map(drop),
1661 ),
1662 (
1663 "OrchardNote",
1664 OrchardNote::read(newer_wallet_note.as_slice()).map(drop),
1665 ),
1666 (
1667 "IronwoodNote",
1668 IronwoodNote::read(newer_wallet_note.as_slice()).map(drop),
1669 ),
1670 (
1671 "OutgoingSaplingNote",
1672 OutgoingSaplingNote::read(newer_outgoing_note.as_slice(), &consensus_parameters)
1673 .map(drop),
1674 ),
1675 (
1676 "OutgoingOrchardNote",
1677 OutgoingOrchardNote::read(newer_outgoing_note.as_slice(), &consensus_parameters)
1678 .map(drop),
1679 ),
1680 (
1681 "OutgoingIronwoodNote",
1682 OutgoingIronwoodNote::read(newer_outgoing_note.as_slice(), &consensus_parameters)
1683 .map(drop),
1684 ),
1685 (
1686 "ShardTrees",
1687 ShardTrees::read(newer_shard_trees.as_slice()).map(drop),
1688 ),
1689 ] {
1690 assert_eq!(
1691 read.expect_err(type_name).kind(),
1692 std::io::ErrorKind::InvalidData,
1693 "{type_name}"
1694 );
1695 }
1696 }
1697
1698 fn v1_nullifier_map_bytes() -> Vec<u8> {
1700 let mut out = Vec::new();
1701 out.write_u8(1).unwrap();
1702 Vector::write(&mut out, &[] as &[()], |_, _| Ok(())).unwrap();
1703 Vector::write(&mut out, &[] as &[()], |_, _| Ok(())).unwrap();
1704 out
1705 }
1706
1707 #[test]
1708 fn nullifier_map_v1_reads_with_empty_ironwood_map() {
1709 let bytes = v1_nullifier_map_bytes();
1710 let map = NullifierMap::read(bytes.as_slice()).expect("v1 should read cleanly");
1711 assert!(map.ironwood.is_empty());
1712 }
1713
1714 #[test]
1715 fn nullifier_map_v2_roundtrip_preserves_ironwood() {
1716 let map = NullifierMap::new();
1717 let mut bytes = Vec::new();
1718 map.write(&mut bytes).expect("write should succeed");
1719 let recovered = NullifierMap::read(bytes.as_slice()).expect("read should succeed");
1720 assert!(recovered.ironwood.is_empty());
1721 }
1722
1723 fn v0_tree_bounds_bytes(
1725 sapling_initial: u32,
1726 sapling_final: u32,
1727 orchard_initial: u32,
1728 orchard_final: u32,
1729 ) -> Vec<u8> {
1730 let mut out = Vec::new();
1731 out.write_u8(0).unwrap();
1732 out.write_u32::<LittleEndian>(sapling_initial).unwrap();
1733 out.write_u32::<LittleEndian>(sapling_final).unwrap();
1734 out.write_u32::<LittleEndian>(orchard_initial).unwrap();
1735 out.write_u32::<LittleEndian>(orchard_final).unwrap();
1736 out
1737 }
1738
1739 #[test]
1740 fn tree_bounds_v0_reads_with_zero_ironwood_sizes() {
1741 let bytes = v0_tree_bounds_bytes(10, 20, 30, 40);
1742 let bounds = TreeBounds::read(bytes.as_slice()).expect("v0 should read cleanly");
1743 assert_eq!(bounds.sapling_initial_tree_size, 10);
1744 assert_eq!(bounds.orchard_final_tree_size, 40);
1745 assert_eq!(bounds.ironwood_initial_tree_size, 0);
1746 assert_eq!(bounds.ironwood_final_tree_size, 0);
1747 }
1748
1749 #[test]
1750 fn tree_bounds_v1_roundtrip() {
1751 let bounds = TreeBounds {
1752 sapling_initial_tree_size: 1,
1753 sapling_final_tree_size: 2,
1754 orchard_initial_tree_size: 3,
1755 orchard_final_tree_size: 4,
1756 ironwood_initial_tree_size: 5,
1757 ironwood_final_tree_size: 6,
1758 };
1759 let mut bytes = Vec::new();
1760 bounds.write(&mut bytes).expect("write should succeed");
1761 let recovered = TreeBounds::read(bytes.as_slice()).expect("read should succeed");
1762 assert_eq!(recovered.ironwood_initial_tree_size, 5);
1763 assert_eq!(recovered.ironwood_final_tree_size, 6);
1764 }
1765
1766 #[test]
1775 fn checkpoint_set_is_reorg_window_plus_pinned_boundaries() {
1776 use crate::shardtree_ext::ShardTreeExt as _;
1777 use crate::witness::{anchor_retention_window, repin_anchor_checkpoints};
1778 use zcash_client_backend::data_api::anchor_retention::{
1779 AnchorRetention, AnchorRetentionInterval,
1780 };
1781
1782 const TIP: u32 = 100_000;
1783 const INTERVAL: u32 = 144;
1784 let policy = AnchorRetention::new(
1785 BlockHeight::from_u32(90_000),
1786 zcash_protocol::zip318::BlockSpacing::UNIFORM,
1787 AnchorRetentionInterval::default(),
1788 );
1789 let mut shard_trees = ShardTrees::new();
1790
1791 for height in (TIP - 2000)..=TIP {
1792 let height = BlockHeight::from_u32(height);
1793 let window = anchor_retention_window(&policy, height);
1794 repin_anchor_checkpoints(&policy, &window, shard_trees.orchard.store_mut());
1795 shard_trees
1796 .orchard
1797 .append_checkpoint(height)
1798 .expect("infallible");
1799 }
1800
1801 let store = shard_trees.orchard.store();
1802 let total = store.checkpoint_count().expect("infallible");
1803 let pinned_ids = store.retained_checkpoints().expect("infallible");
1804 let mut pinned = Vec::new();
1805 let mut rolling = Vec::new();
1806 store
1807 .for_each_checkpoint(total, |id, _| {
1808 if pinned_ids.contains(id) {
1809 pinned.push(u32::from(*id));
1810 } else {
1811 rolling.push(u32::from(*id));
1812 }
1813 Ok(())
1814 })
1815 .expect("infallible");
1816
1817 assert!(
1818 pinned.iter().all(|height| height % INTERVAL == 0),
1819 "every pinned checkpoint must be a grid boundary, got {pinned:?}"
1820 );
1821 assert_eq!(rolling.last().copied(), Some(TIP));
1822 assert_eq!(
1823 (rolling.len(), pinned.len(), total),
1824 (
1825 SHARDTREE_CHECKPOINT_ROLLING_WINDOW_SIZE as usize,
1826 ANCHOR_RETENTION_INTERVALS as usize,
1827 MAX_SHARDTREE_CHECKPOINTS as usize,
1828 ),
1829 "(rolling, pinned, total): the pinned boundaries must not be part of the \
1830 SHARDTREE_CHECKPOINT_ROLLING_WINDOW_SIZE total"
1831 );
1832
1833 for height in (TIP - SHARDTREE_CHECKPOINT_ROLLING_WINDOW_SIZE + 1)..=TIP {
1834 assert!(
1835 store
1836 .get_checkpoint(&BlockHeight::from_u32(height))
1837 .expect("infallible")
1838 .is_some(),
1839 "reorg window is missing height {height}"
1840 );
1841 }
1842
1843 assert!(
1846 rolling
1847 .iter()
1848 .all(|height| *height >= TIP - MAX_SHARDTREE_CHECKPOINTS),
1849 "a rolling checkpoint survived below the reorg window: {rolling:?}"
1850 );
1851
1852 let mut bytes = Vec::new();
1853 shard_trees.write(&mut bytes).expect("write should succeed");
1854 let reloaded = ShardTrees::read(bytes.as_slice()).expect("read should succeed");
1855 let reloaded_store = reloaded.orchard.store();
1856 for boundary in &pinned {
1857 assert!(
1858 reloaded_store
1859 .get_checkpoint(&BlockHeight::from_u32(*boundary))
1860 .expect("infallible")
1861 .is_some(),
1862 "boundary {boundary} lost on reload; a crossing anchored there cannot be built"
1863 );
1864 }
1865 assert_eq!(
1866 reloaded_store.checkpoint_count().expect("infallible"),
1867 MAX_SHARDTREE_CHECKPOINTS as usize
1868 );
1869 }
1870
1871 #[test]
1873 fn shardtree_roundtrip_keeps_newest_checkpoints() {
1874 use crate::shardtree_ext::ShardTreeExt as _;
1875
1876 let mut shard_trees = ShardTrees::new();
1877
1878 for height in 1..=APPENDED {
1879 let height = BlockHeight::from_u32(height);
1880 shard_trees
1881 .sapling
1882 .append_checkpoint(height)
1883 .expect("infallible");
1884 shard_trees
1885 .orchard
1886 .append_checkpoint(height)
1887 .expect("infallible");
1888 }
1889
1890 let mut bytes = Vec::new();
1891 shard_trees.write(&mut bytes).expect("write should succeed");
1892 let roundtripped = ShardTrees::read(bytes.as_slice()).expect("read should succeed");
1893
1894 let sapling_store = roundtripped.sapling.store();
1895 let orchard_store = roundtripped.orchard.store();
1896
1897 let oldest_kept =
1898 BlockHeight::from_u32(APPENDED - SHARDTREE_CHECKPOINT_ROLLING_WINDOW_SIZE + 1);
1899 fn assert_window<S>(store: &S, oldest_kept: BlockHeight)
1900 where
1901 S: ShardStore<CheckpointId = BlockHeight, Error = std::convert::Infallible>,
1902 {
1903 assert_eq!(
1904 store.checkpoint_count().expect("infallible"),
1905 SHARDTREE_CHECKPOINT_ROLLING_WINDOW_SIZE as usize
1906 );
1907 assert_eq!(
1908 store.min_checkpoint_id().expect("infallible"),
1909 Some(oldest_kept)
1910 );
1911 assert_eq!(
1912 store.max_checkpoint_id().expect("infallible"),
1913 Some(BlockHeight::from_u32(APPENDED))
1914 );
1915 assert!(
1916 store
1917 .get_checkpoint(&(oldest_kept - 1))
1918 .expect("infallible")
1919 .is_none()
1920 );
1921 }
1922 assert_window(sapling_store, oldest_kept);
1923 assert_window(orchard_store, oldest_kept);
1924 }
1925
1926 #[test]
1928 fn shardtree_read_adds_initialization_checkpoint_when_blob_has_none() {
1929 fn checkpoint_less<H, const DEPTH: u8, const SHARD_HEIGHT: u8>()
1930 -> ShardTree<MemoryShardStore<H, BlockHeight>, DEPTH, SHARD_HEIGHT>
1931 where
1932 H: Hashable + Clone + PartialEq,
1933 {
1934 ShardTree::new(
1935 MemoryShardStore::empty(),
1936 SHARDTREE_CHECKPOINT_ROLLING_WINDOW_SIZE as usize,
1937 )
1938 }
1939
1940 let mut shard_trees = ShardTrees {
1941 sapling: checkpoint_less(),
1942 orchard: checkpoint_less(),
1943 ironwood: checkpoint_less(),
1944 };
1945 assert_eq!(
1946 shard_trees
1947 .sapling
1948 .store()
1949 .max_checkpoint_id()
1950 .expect("infallible"),
1951 None
1952 );
1953
1954 let mut bytes = Vec::new();
1955 shard_trees.write(&mut bytes).expect("write should succeed");
1956 let roundtripped = ShardTrees::read(bytes.as_slice()).expect("read should succeed");
1957
1958 fn assert_initialization_checkpoint<S>(store: &S)
1959 where
1960 S: ShardStore<CheckpointId = BlockHeight, Error = std::convert::Infallible>,
1961 {
1962 assert_eq!(store.checkpoint_count().expect("infallible"), 1);
1963 assert_eq!(
1964 store.max_checkpoint_id().expect("infallible"),
1965 Some(BlockHeight::from_u32(0))
1966 );
1967 }
1968 assert_initialization_checkpoint(roundtripped.sapling.store());
1969 assert_initialization_checkpoint(roundtripped.orchard.store());
1970 assert_initialization_checkpoint(roundtripped.ironwood.store());
1971 }
1972
1973 #[test]
1977 fn shardtree_roundtrip_keeps_pinned_anchor_checkpoints() {
1978 use crate::shardtree_ext::ShardTreeExt as _;
1979
1980 let pinned = BlockHeight::from_u32(24);
1981 let mut shard_trees = ShardTrees::new();
1982
1983 shard_trees
1984 .sapling
1985 .store_mut()
1986 .add_retained_checkpoint(pinned)
1987 .expect("infallible");
1988 for height in 1..=APPENDED {
1989 shard_trees
1990 .sapling
1991 .append_checkpoint(BlockHeight::from_u32(height))
1992 .expect("infallible");
1993 }
1994
1995 assert!(
1996 shard_trees
1997 .sapling
1998 .store()
1999 .get_checkpoint(&pinned)
2000 .expect("infallible")
2001 .is_some(),
2002 "pruning must not evict a pinned checkpoint"
2003 );
2004
2005 let mut bytes = Vec::new();
2006 shard_trees.write(&mut bytes).expect("write should succeed");
2007 let roundtripped = ShardTrees::read(bytes.as_slice()).expect("read should succeed");
2008
2009 let sapling_store = roundtripped.sapling.store();
2010 assert!(
2011 sapling_store
2012 .get_checkpoint(&pinned)
2013 .expect("infallible")
2014 .is_some(),
2015 "serialization must not evict a pinned checkpoint"
2016 );
2017 assert_eq!(
2018 sapling_store.checkpoint_count().expect("infallible"),
2019 SHARDTREE_CHECKPOINT_ROLLING_WINDOW_SIZE as usize + 1
2020 );
2021 assert!(
2022 sapling_store
2023 .retained_checkpoints()
2024 .expect("infallible")
2025 .is_empty()
2026 );
2027 }
2028}