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| {
1064 write_string(
1065 w,
1066 &unified_address.encode_receiver_preserving(consensus_parameters),
1067 )
1068 },
1069 )?;
1070
1071 Ok(())
1072 }
1073}
1074
1075fn read_orchard_protocol_outgoing_note<R: Read, P>(
1079 mut reader: R,
1080 consensus_parameters: &impl consensus::Parameters,
1081 note_version: orchard::note::NoteVersion,
1082) -> std::io::Result<OutgoingNote<orchard::Note, P>> {
1083 let version = read_version(
1084 &mut reader,
1085 "OutgoingNote",
1086 OutgoingNote::<orchard::Note, P>::serialized_version(),
1087 )?;
1088
1089 let txid = TxId::read(&mut reader)?;
1090 let output_index = if version >= 1 {
1091 reader.read_u32::<LittleEndian>()?
1092 } else {
1093 u32::from(reader.read_u16::<LittleEndian>()?)
1094 };
1095
1096 let account_id =
1097 zip32::AccountId::try_from(reader.read_u32::<LittleEndian>()?).map_err(|e| {
1098 std::io::Error::new(
1099 std::io::ErrorKind::InvalidData,
1100 format!("failed to read account id. {e}"),
1101 )
1102 })?;
1103 let scope = match reader.read_u8()? {
1104 0 => Ok(zip32::Scope::External),
1105 1 => Ok(zip32::Scope::Internal),
1106 _ => Err(std::io::Error::new(
1107 std::io::ErrorKind::InvalidData,
1108 "invalid scope value",
1109 )),
1110 }?;
1111
1112 let mut address_bytes = [0u8; 43];
1113 reader.read_exact(&mut address_bytes)?;
1114 let recipient = orchard::Address::from_raw_address_bytes(&address_bytes)
1115 .expect("should be a valid address");
1116 let value = orchard::value::NoteValue::from_raw(reader.read_u64::<LittleEndian>()?);
1117 let mut rho_bytes = [0u8; 32];
1118 reader.read_exact(&mut rho_bytes)?;
1119 let rho = orchard::note::Rho::from_bytes(&rho_bytes).expect("should be valid rho bytes");
1120 let mut rseed_bytes = [0u8; 32];
1121 reader.read_exact(&mut rseed_bytes)?;
1122 let rseed = orchard::note::RandomSeed::from_bytes(rseed_bytes, &rho)
1123 .expect("should be valid random seed bytes");
1124
1125 let mut memo_bytes = [0u8; 512];
1126 reader.read_exact(&mut memo_bytes)?;
1127 let memo = Memo::from_bytes(&memo_bytes).map_err(|e| {
1128 std::io::Error::new(
1129 std::io::ErrorKind::InvalidData,
1130 format!("failed to read memo. {e}"),
1131 )
1132 })?;
1133
1134 let recipient_unified_address = Optional::read(&mut reader, |r| {
1135 let encoded_address = read_string(r)?;
1136
1137 decode_unified_address(consensus_parameters, &encoded_address)
1138 })?;
1139
1140 Ok(OutgoingNote {
1141 output_id: OutputId::new(txid, output_index),
1142 key_id: KeyId::from_parts(account_id, scope),
1143 note: orchard::note::Note::from_parts(recipient, value, rho, rseed, note_version)
1144 .expect("should be a valid orchard note"),
1145 memo,
1146 recipient_full_unified_address: recipient_unified_address,
1147 marker: std::marker::PhantomData,
1148 })
1149}
1150
1151fn write_orchard_protocol_outgoing_note<W: Write, P>(
1153 note: &OutgoingNote<orchard::Note, P>,
1154 mut writer: W,
1155 consensus_parameters: &impl consensus::Parameters,
1156) -> std::io::Result<()> {
1157 writer.write_u8(OutgoingNote::<orchard::Note, P>::serialized_version())?;
1158
1159 note.output_id.txid().write(&mut writer)?;
1160 writer.write_u32::<LittleEndian>(note.output_id.output_index())?;
1161
1162 writer.write_u32::<LittleEndian>(note.key_id.account_id.into())?;
1163 writer.write_u8(note.key_id.scope as u8)?;
1164
1165 writer.write_all(¬e.note.recipient().to_raw_address_bytes())?;
1166 writer.write_u64::<LittleEndian>(note.note.value().inner())?;
1167 writer.write_all(¬e.note.rho().to_bytes())?;
1168 writer.write_all(note.note.rseed().as_bytes())?;
1169
1170 writer.write_all(note.memo.encode().as_array())?;
1171 Optional::write(
1172 &mut writer,
1173 note.recipient_full_unified_address.as_ref(),
1174 |w, unified_address| {
1175 write_string(
1176 w,
1177 &unified_address.encode_receiver_preserving(consensus_parameters),
1178 )
1179 },
1180 )?;
1181
1182 Ok(())
1183}
1184
1185impl OutgoingOrchardNote {
1186 pub fn read<R: Read>(
1188 reader: R,
1189 consensus_parameters: &impl consensus::Parameters,
1190 ) -> std::io::Result<Self> {
1191 read_orchard_protocol_outgoing_note(
1192 reader,
1193 consensus_parameters,
1194 orchard::note::NoteVersion::V2,
1195 )
1196 }
1197
1198 pub fn write<W: Write>(
1200 &self,
1201 writer: W,
1202 consensus_parameters: &impl consensus::Parameters,
1203 ) -> std::io::Result<()> {
1204 write_orchard_protocol_outgoing_note(self, writer, consensus_parameters)
1205 }
1206}
1207
1208impl OutgoingIronwoodNote {
1209 pub fn read<R: Read>(
1211 reader: R,
1212 consensus_parameters: &impl consensus::Parameters,
1213 ) -> std::io::Result<Self> {
1214 read_orchard_protocol_outgoing_note(
1215 reader,
1216 consensus_parameters,
1217 orchard::note::NoteVersion::V3,
1218 )
1219 }
1220
1221 pub fn write<W: Write>(
1223 &self,
1224 writer: W,
1225 consensus_parameters: &impl consensus::Parameters,
1226 ) -> std::io::Result<()> {
1227 write_orchard_protocol_outgoing_note(self, writer, consensus_parameters)
1228 }
1229}
1230
1231impl ShardTrees {
1232 fn serialized_version() -> u8 {
1233 1
1235 }
1236
1237 pub fn read<R: Read>(mut reader: R) -> std::io::Result<Self> {
1239 let version = read_version(&mut reader, "ShardTrees", Self::serialized_version())?;
1240 let sapling = Self::read_shardtree(&mut reader)?;
1241 let orchard = Self::read_shardtree(&mut reader)?;
1242 let ironwood = if version >= 1 {
1243 Self::read_shardtree(&mut reader)?
1244 } else {
1245 Self::new().ironwood
1247 };
1248
1249 Ok(Self {
1250 sapling,
1251 orchard,
1252 ironwood,
1253 })
1254 }
1255
1256 pub fn write<W: Write>(&mut self, mut writer: W) -> std::io::Result<()> {
1258 writer.write_u8(Self::serialized_version())?;
1259 Self::write_shardtree(&mut writer, &mut self.sapling)?;
1260 Self::write_shardtree(&mut writer, &mut self.orchard)?;
1261 Self::write_shardtree(&mut writer, &mut self.ironwood)?;
1262
1263 Ok(())
1264 }
1265
1266 fn read_shardtree<
1267 H: Hashable + Clone + HashSer + Eq,
1268 C: Ord + std::fmt::Debug + Copy + From<u32>,
1269 R: Read,
1270 const DEPTH: u8,
1271 const SHARD_HEIGHT: u8,
1272 >(
1273 mut reader: R,
1274 ) -> std::io::Result<ShardTree<MemoryShardStore<H, C>, DEPTH, SHARD_HEIGHT>> {
1275 let shards = Vector::read(&mut reader, |r| {
1276 let level = incrementalmerkletree::Level::from(r.read_u8()?);
1277 let index = r.read_u64::<LittleEndian>()?;
1278 let root_addr = incrementalmerkletree::Address::from_parts(level, index);
1279 let shard = read_shard(r)?;
1280
1281 LocatedPrunableTree::from_parts(root_addr, shard).map_err(|addr| {
1282 std::io::Error::new(
1283 std::io::ErrorKind::InvalidData,
1284 format!("parent node in root has level 0 relative to root address: {addr:?}"),
1285 )
1286 })
1287 })?;
1288 let mut store = MemoryShardStore::empty();
1289 for shard in shards {
1290 store.put_shard(shard).expect("infallible");
1291 }
1292 let checkpoints = Vector::read(&mut reader, |r| {
1293 let checkpoint_id = C::from(r.read_u32::<LittleEndian>()?);
1294 let tree_state = match r.read_u8()? {
1295 0 => TreeState::Empty,
1296 1 => TreeState::AtPosition(Position::from(r.read_u64::<LittleEndian>()?)),
1297 otherwise => {
1298 return Err(std::io::Error::new(
1299 std::io::ErrorKind::InvalidData,
1300 format!(
1301 "failed to read TreeState. expected boolean value, found {otherwise}"
1302 ),
1303 ));
1304 }
1305 };
1306 let marks_removed =
1307 Vector::read(r, |r| r.read_u64::<LittleEndian>().map(Position::from))?;
1308 Ok((
1309 checkpoint_id,
1310 Checkpoint::from_parts(tree_state, marks_removed.into_iter().collect()),
1311 ))
1312 })?;
1313 if checkpoints.is_empty() {
1314 store
1315 .add_checkpoint(C::from(0), Checkpoint::tree_empty())
1316 .expect("Infallible");
1317 }
1318 for (checkpoint_id, checkpoint) in checkpoints {
1319 store
1320 .add_checkpoint(checkpoint_id, checkpoint)
1321 .expect("Infallible");
1322 }
1323 store.put_cap(read_shard(reader)?).expect("Infallible");
1324
1325 Ok(shardtree::ShardTree::new(
1326 store,
1327 SHARDTREE_CHECKPOINT_ROLLING_WINDOW_SIZE as usize,
1328 ))
1329 }
1330
1331 fn write_shardtree<
1333 H: Hashable + Clone + Eq + HashSer,
1334 C: Ord + std::fmt::Debug + Copy,
1335 W: Write,
1336 const DEPTH: u8,
1337 const SHARD_HEIGHT: u8,
1338 >(
1339 mut writer: W,
1340 shardtree: &mut ShardTree<MemoryShardStore<H, C>, DEPTH, SHARD_HEIGHT>,
1341 ) -> std::io::Result<()>
1342 where
1343 u32: From<C>,
1344 {
1345 fn write_shards<W, H, C>(
1346 mut writer: W,
1347 store: &MemoryShardStore<H, C>,
1348 ) -> std::io::Result<()>
1349 where
1350 H: Hashable + Clone + Eq + HashSer,
1351 C: Ord + std::fmt::Debug + Copy,
1352 W: Write,
1353 {
1354 let roots = store.get_shard_roots().expect("Infallible");
1355 Vector::write(&mut writer, &roots, |w, root| {
1356 w.write_u8(root.level().into())?;
1357 w.write_u64::<LittleEndian>(root.index())?;
1358 let shard = store
1359 .get_shard(*root)
1360 .expect("Infallible")
1361 .expect("cannot find root that shard store claims to have");
1362 write_shard(w, shard.root())
1363 })
1364 }
1365
1366 fn write_checkpoints<W, Cid>(
1367 mut writer: W,
1368 checkpoints: &[(Cid, Checkpoint)],
1369 ) -> std::io::Result<()>
1370 where
1371 W: Write,
1372 Cid: Ord + std::fmt::Debug + Copy,
1373 u32: From<Cid>,
1374 {
1375 Vector::write(
1376 &mut writer,
1377 checkpoints,
1378 |mut w, (checkpoint_id, checkpoint)| {
1379 w.write_u32::<LittleEndian>(u32::from(*checkpoint_id))?;
1380 match checkpoint.tree_state() {
1381 shardtree::store::TreeState::Empty => w.write_u8(0),
1382 shardtree::store::TreeState::AtPosition(pos) => {
1383 w.write_u8(1)?;
1384 w.write_u64::<LittleEndian>(<u64 as From<Position>>::from(pos))
1385 }
1386 }?;
1387 Vector::write(
1388 &mut w,
1389 &checkpoint.marks_removed().iter().collect::<Vec<_>>(),
1390 |w, mark| {
1391 w.write_u64::<LittleEndian>(<u64 as From<Position>>::from(**mark))
1392 },
1393 )
1394 },
1395 )
1396 }
1397
1398 let mut store = std::mem::replace(
1400 shardtree,
1401 shardtree::ShardTree::new(MemoryShardStore::empty(), 0),
1402 )
1403 .into_store();
1404
1405 macro_rules! write_with_error_handling {
1406 ($writer: ident, $from: ident) => {
1407 if let Err(e) = $writer(&mut writer, &$from) {
1408 *shardtree = shardtree::ShardTree::new(
1409 store,
1410 SHARDTREE_CHECKPOINT_ROLLING_WINDOW_SIZE as usize,
1411 );
1412 return Err(e);
1413 }
1414 };
1415 }
1416
1417 write_with_error_handling!(write_shards, store);
1419
1420 let mut checkpoints = Vec::new();
1422 let checkpoint_count = store.checkpoint_count().expect("Infallible");
1423 store
1424 .with_checkpoints(checkpoint_count, |checkpoint_id, checkpoint| {
1425 checkpoints.push((*checkpoint_id, checkpoint.clone()));
1426 Ok(())
1427 })
1428 .expect("Infallible");
1429 write_with_error_handling!(write_checkpoints, checkpoints);
1430
1431 let cap = store.get_cap().expect("Infallible");
1433 write_with_error_handling!(write_shard, cap);
1434
1435 *shardtree =
1436 shardtree::ShardTree::new(store, SHARDTREE_CHECKPOINT_ROLLING_WINDOW_SIZE as usize);
1437
1438 Ok(())
1439 }
1440}
1441
1442#[cfg(test)]
1443mod tests {
1444 use crate::{sync::MAX_SHARDTREE_CHECKPOINTS, witness::ANCHOR_RETENTION_INTERVALS};
1445
1446 use super::*;
1447
1448 fn written(scan_target: &ScanTarget) -> Vec<u8> {
1449 let mut bytes = Vec::new();
1450 scan_target.write(&mut bytes).unwrap();
1451 bytes
1452 }
1453
1454 #[test]
1455 fn scan_target_round_trips_with_its_pools_and_origin() {
1456 let txid = TxId::from_bytes([3; 32]);
1457 for scan_target in [
1458 ScanTarget::new(BlockHeight::from_u32(10), None),
1459 ScanTarget::new(BlockHeight::from_u32(10), Some(txid)),
1460 ScanTarget::internal(BlockHeight::from_u32(20), txid)
1461 .with_pool(zcash_protocol::ShieldedPool::Sapling)
1462 .with_pool(zcash_protocol::ShieldedPool::Ironwood),
1463 ] {
1464 let read = ScanTarget::read(written(&scan_target).as_slice()).unwrap();
1465 assert_eq!(read, scan_target);
1466 assert_eq!(read.txid(), scan_target.txid());
1467 assert_eq!(read.pools(), scan_target.pools());
1468 assert_eq!(read.origin(), scan_target.origin());
1469 }
1470 }
1471
1472 #[test]
1475 fn scan_target_version_zero_is_read_as_external() {
1476 let v0 = |txid: [u8; 32]| {
1477 let mut bytes = vec![0u8];
1478 bytes.write_u32::<LittleEndian>(30).unwrap();
1479 bytes.extend_from_slice(&txid);
1480 bytes.push(1);
1481 bytes
1482 };
1483
1484 let without_transaction = ScanTarget::read(v0([0; 32]).as_slice()).unwrap();
1485 assert_eq!(
1486 without_transaction,
1487 ScanTarget::new(BlockHeight::from_u32(30), None)
1488 );
1489 assert_eq!(without_transaction.txid(), None);
1490 assert_eq!(without_transaction.origin(), ScanTargetOrigin::External);
1491 assert!(without_transaction.pools().is_empty());
1492
1493 let with_transaction = ScanTarget::read(v0([5; 32]).as_slice()).unwrap();
1494 assert_eq!(with_transaction.txid(), Some(TxId::from_bytes([5; 32])));
1495 }
1496
1497 #[test]
1498 fn scan_target_with_an_unknown_pool_or_origin_is_refused() {
1499 let mut unknown_pool = written(&ScanTarget::new(BlockHeight::from_u32(10), None));
1500 *unknown_pool.last_mut().unwrap() = 0;
1501 let pools_index = unknown_pool.len() - 2;
1502 unknown_pool[pools_index] = 1 << 3;
1503 assert_eq!(
1504 ScanTarget::read(unknown_pool.as_slice())
1505 .unwrap_err()
1506 .kind(),
1507 std::io::ErrorKind::InvalidData
1508 );
1509
1510 let mut unknown_origin = written(&ScanTarget::new(BlockHeight::from_u32(10), None));
1511 *unknown_origin.last_mut().unwrap() = 2;
1512 assert_eq!(
1513 ScanTarget::read(unknown_origin.as_slice())
1514 .unwrap_err()
1515 .kind(),
1516 std::io::ErrorKind::InvalidData
1517 );
1518 }
1519
1520 fn v3_sync_state_bytes() -> Vec<u8> {
1524 let mut out = Vec::new();
1525 out.write_u8(3).unwrap();
1526 Vector::write(&mut out, &[] as &[()], |_, _| Ok(())).unwrap();
1527 Vector::write(&mut out, &[] as &[()], |_, _| Ok(())).unwrap();
1528 Vector::write(&mut out, &[] as &[()], |_, _| Ok(())).unwrap();
1529 Vector::write(&mut out, &[] as &[()], |_, _| Ok(())).unwrap();
1530 out
1531 }
1532
1533 #[test]
1534 fn sync_state_v3_reads_with_empty_ironwood_ranges() {
1535 let bytes = v3_sync_state_bytes();
1536 let sync_state = SyncState::read(bytes.as_slice()).expect("v3 should read cleanly");
1537 assert!(sync_state.ironwood_shard_ranges().is_empty());
1538 }
1539
1540 #[test]
1541 fn sync_state_v4_roundtrip_preserves_ironwood_ranges() {
1542 let mut state = SyncState::new();
1543 state.ironwood_shard_ranges = vec![
1544 BlockHeight::from_u32(100)..BlockHeight::from_u32(200),
1545 BlockHeight::from_u32(300)..BlockHeight::from_u32(400),
1546 ];
1547 state.scan_ranges.push(ScanRange::from_parts(
1548 BlockHeight::from_u32(100)..BlockHeight::from_u32(400),
1549 ScanPriority::Historic,
1550 ));
1551 let mut bytes = Vec::new();
1552 state.write(&mut bytes).expect("write should succeed");
1553 let recovered = SyncState::read(bytes.as_slice()).expect("read should succeed");
1554 assert_eq!(recovered.ironwood_shard_ranges, state.ironwood_shard_ranges);
1555 assert_eq!(recovered.scan_ranges, state.scan_ranges);
1556 }
1557
1558 fn v4_sync_state_bytes() -> Vec<u8> {
1562 let mut out = Vec::new();
1563 out.write_u8(4).unwrap();
1564 Vector::write(&mut out, &[] as &[()], |_, _| Ok(())).unwrap();
1565 Vector::write(&mut out, &[] as &[()], |_, _| Ok(())).unwrap();
1566 Vector::write(&mut out, &[] as &[()], |_, _| Ok(())).unwrap();
1567 Vector::write(&mut out, &[] as &[()], |_, _| Ok(())).unwrap();
1568 Vector::write(&mut out, &[] as &[()], |_, _| Ok(())).unwrap();
1569 out
1570 }
1571
1572 #[test]
1573 fn sync_state_v4_reads_with_unset_transparent_scan_floor() {
1574 let bytes = v4_sync_state_bytes();
1575 let sync_state = SyncState::read(bytes.as_slice()).expect("v4 should read cleanly");
1576 assert_eq!(sync_state.transparent_scan_floor, None);
1577 }
1578
1579 #[test]
1580 fn sync_state_v5_roundtrip_preserves_transparent_scan_floor() {
1581 for transparent_scan_floor in [None, Some(BlockHeight::from_u32(100))] {
1582 let mut state = SyncState::new();
1583 state.transparent_scan_floor = transparent_scan_floor;
1584 let mut bytes = Vec::new();
1585 state.write(&mut bytes).expect("write should succeed");
1586 let recovered = SyncState::read(bytes.as_slice()).expect("read should succeed");
1587 assert_eq!(recovered.transparent_scan_floor, transparent_scan_floor);
1588 }
1589 }
1590
1591 mod read_version {
1592 use super::super::read_version;
1593
1594 const ARBITRARY_CURRENT_VERSION: u8 = 2;
1596
1597 #[test]
1598 fn accepts_versions_up_to_the_current_version() {
1599 for version in [0, ARBITRARY_CURRENT_VERSION] {
1600 assert_eq!(
1601 read_version([version].as_slice(), "Type", ARBITRARY_CURRENT_VERSION).unwrap(),
1602 version
1603 );
1604 }
1605 }
1606
1607 #[test]
1608 fn refuses_a_version_above_the_current_version() {
1609 let error = read_version(
1610 [ARBITRARY_CURRENT_VERSION + 1].as_slice(),
1611 "Type",
1612 ARBITRARY_CURRENT_VERSION,
1613 )
1614 .expect_err("a version above the current version is refused");
1615 assert_eq!(error.kind(), std::io::ErrorKind::InvalidData);
1616 }
1617 }
1618
1619 #[test]
1622 fn readers_refuse_serialized_versions_above_their_own() {
1623 let consensus_parameters = zcash_protocol::consensus::MAIN_NETWORK;
1624 let newer_scan_target = [ScanTarget::serialized_version() + 1];
1625 let newer_sync_state = [SyncState::serialized_version() + 1];
1626 let newer_tree_bounds = [TreeBounds::serialized_version() + 1];
1627 let newer_nullifier_map = [NullifierMap::serialized_version() + 1];
1628 let newer_wallet_block = [WalletBlock::serialized_version() + 1];
1629 let newer_wallet_transaction = [WalletTransaction::serialized_version() + 1];
1630 let newer_transparent_coin = [TransparentCoin::serialized_version() + 1];
1631 let newer_wallet_note = [SaplingNote::serialized_version() + 1];
1632 let newer_outgoing_note = [OutgoingSaplingNote::serialized_version() + 1];
1633 let newer_shard_trees = [ShardTrees::serialized_version() + 1];
1634
1635 for (type_name, read) in [
1636 (
1637 "ScanTarget",
1638 ScanTarget::read(newer_scan_target.as_slice()).map(drop),
1639 ),
1640 (
1641 "SyncState",
1642 SyncState::read(newer_sync_state.as_slice()).map(drop),
1643 ),
1644 (
1645 "TreeBounds",
1646 TreeBounds::read(newer_tree_bounds.as_slice()).map(drop),
1647 ),
1648 (
1649 "NullifierMap",
1650 NullifierMap::read(newer_nullifier_map.as_slice()).map(drop),
1651 ),
1652 (
1653 "WalletBlock",
1654 WalletBlock::read(newer_wallet_block.as_slice()).map(drop),
1655 ),
1656 (
1657 "WalletTransaction",
1658 WalletTransaction::read(newer_wallet_transaction.as_slice(), &consensus_parameters)
1659 .map(drop),
1660 ),
1661 (
1662 "TransparentCoin",
1663 TransparentCoin::read(newer_transparent_coin.as_slice()).map(drop),
1664 ),
1665 (
1666 "SaplingNote",
1667 SaplingNote::read(newer_wallet_note.as_slice()).map(drop),
1668 ),
1669 (
1670 "OrchardNote",
1671 OrchardNote::read(newer_wallet_note.as_slice()).map(drop),
1672 ),
1673 (
1674 "IronwoodNote",
1675 IronwoodNote::read(newer_wallet_note.as_slice()).map(drop),
1676 ),
1677 (
1678 "OutgoingSaplingNote",
1679 OutgoingSaplingNote::read(newer_outgoing_note.as_slice(), &consensus_parameters)
1680 .map(drop),
1681 ),
1682 (
1683 "OutgoingOrchardNote",
1684 OutgoingOrchardNote::read(newer_outgoing_note.as_slice(), &consensus_parameters)
1685 .map(drop),
1686 ),
1687 (
1688 "OutgoingIronwoodNote",
1689 OutgoingIronwoodNote::read(newer_outgoing_note.as_slice(), &consensus_parameters)
1690 .map(drop),
1691 ),
1692 (
1693 "ShardTrees",
1694 ShardTrees::read(newer_shard_trees.as_slice()).map(drop),
1695 ),
1696 ] {
1697 assert_eq!(
1698 read.expect_err(type_name).kind(),
1699 std::io::ErrorKind::InvalidData,
1700 "{type_name}"
1701 );
1702 }
1703 }
1704
1705 fn v1_nullifier_map_bytes() -> Vec<u8> {
1707 let mut out = Vec::new();
1708 out.write_u8(1).unwrap();
1709 Vector::write(&mut out, &[] as &[()], |_, _| Ok(())).unwrap();
1710 Vector::write(&mut out, &[] as &[()], |_, _| Ok(())).unwrap();
1711 out
1712 }
1713
1714 #[test]
1715 fn nullifier_map_v1_reads_with_empty_ironwood_map() {
1716 let bytes = v1_nullifier_map_bytes();
1717 let map = NullifierMap::read(bytes.as_slice()).expect("v1 should read cleanly");
1718 assert!(map.ironwood.is_empty());
1719 }
1720
1721 #[test]
1722 fn nullifier_map_v2_roundtrip_preserves_ironwood() {
1723 let map = NullifierMap::new();
1724 let mut bytes = Vec::new();
1725 map.write(&mut bytes).expect("write should succeed");
1726 let recovered = NullifierMap::read(bytes.as_slice()).expect("read should succeed");
1727 assert!(recovered.ironwood.is_empty());
1728 }
1729
1730 fn v0_tree_bounds_bytes(
1732 sapling_initial: u32,
1733 sapling_final: u32,
1734 orchard_initial: u32,
1735 orchard_final: u32,
1736 ) -> Vec<u8> {
1737 let mut out = Vec::new();
1738 out.write_u8(0).unwrap();
1739 out.write_u32::<LittleEndian>(sapling_initial).unwrap();
1740 out.write_u32::<LittleEndian>(sapling_final).unwrap();
1741 out.write_u32::<LittleEndian>(orchard_initial).unwrap();
1742 out.write_u32::<LittleEndian>(orchard_final).unwrap();
1743 out
1744 }
1745
1746 #[test]
1747 fn tree_bounds_v0_reads_with_zero_ironwood_sizes() {
1748 let bytes = v0_tree_bounds_bytes(10, 20, 30, 40);
1749 let bounds = TreeBounds::read(bytes.as_slice()).expect("v0 should read cleanly");
1750 assert_eq!(bounds.sapling_initial_tree_size, 10);
1751 assert_eq!(bounds.orchard_final_tree_size, 40);
1752 assert_eq!(bounds.ironwood_initial_tree_size, 0);
1753 assert_eq!(bounds.ironwood_final_tree_size, 0);
1754 }
1755
1756 #[test]
1757 fn tree_bounds_v1_roundtrip() {
1758 let bounds = TreeBounds {
1759 sapling_initial_tree_size: 1,
1760 sapling_final_tree_size: 2,
1761 orchard_initial_tree_size: 3,
1762 orchard_final_tree_size: 4,
1763 ironwood_initial_tree_size: 5,
1764 ironwood_final_tree_size: 6,
1765 };
1766 let mut bytes = Vec::new();
1767 bounds.write(&mut bytes).expect("write should succeed");
1768 let recovered = TreeBounds::read(bytes.as_slice()).expect("read should succeed");
1769 assert_eq!(recovered.ironwood_initial_tree_size, 5);
1770 assert_eq!(recovered.ironwood_final_tree_size, 6);
1771 }
1772
1773 #[test]
1782 fn checkpoint_set_is_reorg_window_plus_pinned_boundaries() {
1783 use crate::shardtree_ext::ShardTreeExt as _;
1784 use crate::witness::{anchor_retention_window, repin_anchor_checkpoints};
1785 use zcash_client_backend::data_api::anchor_retention::{
1786 AnchorRetention, AnchorRetentionInterval,
1787 };
1788
1789 const TIP: u32 = 100_000;
1790 const INTERVAL: u32 = 144;
1791 let policy = AnchorRetention::new(
1792 BlockHeight::from_u32(90_000),
1793 AnchorRetentionInterval::default(),
1794 );
1795 let mut shard_trees = ShardTrees::new();
1796
1797 for height in (TIP - 2000)..=TIP {
1798 let height = BlockHeight::from_u32(height);
1799 let window = anchor_retention_window(&policy, height);
1800 repin_anchor_checkpoints(&policy, &window, shard_trees.orchard.store_mut());
1801 shard_trees
1802 .orchard
1803 .append_checkpoint(height)
1804 .expect("infallible");
1805 }
1806
1807 let store = shard_trees.orchard.store();
1808 let total = store.checkpoint_count().expect("infallible");
1809 let pinned_ids = store.retained_checkpoints().expect("infallible");
1810 let mut pinned = Vec::new();
1811 let mut rolling = Vec::new();
1812 store
1813 .for_each_checkpoint(total, |id, _| {
1814 if pinned_ids.contains(id) {
1815 pinned.push(u32::from(*id));
1816 } else {
1817 rolling.push(u32::from(*id));
1818 }
1819 Ok(())
1820 })
1821 .expect("infallible");
1822
1823 assert!(
1824 pinned.iter().all(|height| height % INTERVAL == 0),
1825 "every pinned checkpoint must be a grid boundary, got {pinned:?}"
1826 );
1827 assert_eq!(rolling.last().copied(), Some(TIP));
1828 assert_eq!(
1829 (rolling.len(), pinned.len(), total),
1830 (
1831 SHARDTREE_CHECKPOINT_ROLLING_WINDOW_SIZE as usize,
1832 ANCHOR_RETENTION_INTERVALS as usize,
1833 MAX_SHARDTREE_CHECKPOINTS as usize,
1834 ),
1835 "(rolling, pinned, total): the pinned boundaries must not be part of the \
1836 SHARDTREE_CHECKPOINT_ROLLING_WINDOW_SIZE total"
1837 );
1838
1839 for height in (TIP - SHARDTREE_CHECKPOINT_ROLLING_WINDOW_SIZE + 1)..=TIP {
1840 assert!(
1841 store
1842 .get_checkpoint(&BlockHeight::from_u32(height))
1843 .expect("infallible")
1844 .is_some(),
1845 "reorg window is missing height {height}"
1846 );
1847 }
1848
1849 assert!(
1850 rolling
1851 .iter()
1852 .all(|height| *height >= TIP - SHARDTREE_CHECKPOINT_ROLLING_WINDOW_SIZE),
1853 "a rolling checkpoint survived below the reorg window: {rolling:?}"
1854 );
1855
1856 let mut bytes = Vec::new();
1857 shard_trees.write(&mut bytes).expect("write should succeed");
1858 let reloaded = ShardTrees::read(bytes.as_slice()).expect("read should succeed");
1859 let reloaded_store = reloaded.orchard.store();
1860 for boundary in &pinned {
1861 assert!(
1862 reloaded_store
1863 .get_checkpoint(&BlockHeight::from_u32(*boundary))
1864 .expect("infallible")
1865 .is_some(),
1866 "boundary {boundary} lost on reload; a crossing anchored there cannot be built"
1867 );
1868 }
1869 assert_eq!(
1870 reloaded_store.checkpoint_count().expect("infallible"),
1871 MAX_SHARDTREE_CHECKPOINTS as usize
1872 );
1873 }
1874
1875 #[test]
1877 fn shardtree_roundtrip_keeps_newest_checkpoints() {
1878 use crate::shardtree_ext::ShardTreeExt as _;
1879
1880 let mut shard_trees = ShardTrees::new();
1881
1882 for height in 1..=150 {
1883 let height = BlockHeight::from_u32(height);
1884 shard_trees
1885 .sapling
1886 .append_checkpoint(height)
1887 .expect("infallible");
1888 shard_trees
1889 .orchard
1890 .append_checkpoint(height)
1891 .expect("infallible");
1892 }
1893
1894 let mut bytes = Vec::new();
1895 shard_trees.write(&mut bytes).expect("write should succeed");
1896 let roundtripped = ShardTrees::read(bytes.as_slice()).expect("read should succeed");
1897
1898 let sapling_store = roundtripped.sapling.store();
1899 let orchard_store = roundtripped.orchard.store();
1900
1901 let oldest_kept = BlockHeight::from_u32(150 - SHARDTREE_CHECKPOINT_ROLLING_WINDOW_SIZE + 1);
1902 fn assert_window<S>(store: &S, oldest_kept: BlockHeight)
1903 where
1904 S: ShardStore<CheckpointId = BlockHeight, Error = std::convert::Infallible>,
1905 {
1906 assert_eq!(
1907 store.checkpoint_count().expect("infallible"),
1908 SHARDTREE_CHECKPOINT_ROLLING_WINDOW_SIZE as usize
1909 );
1910 assert_eq!(
1911 store.min_checkpoint_id().expect("infallible"),
1912 Some(oldest_kept)
1913 );
1914 assert_eq!(
1915 store.max_checkpoint_id().expect("infallible"),
1916 Some(BlockHeight::from_u32(150))
1917 );
1918 assert!(
1919 store
1920 .get_checkpoint(&(oldest_kept - 1))
1921 .expect("infallible")
1922 .is_none()
1923 );
1924 }
1925 assert_window(sapling_store, oldest_kept);
1926 assert_window(orchard_store, oldest_kept);
1927 }
1928
1929 #[test]
1931 fn shardtree_read_adds_initialization_checkpoint_when_blob_has_none() {
1932 fn checkpoint_less<H, const DEPTH: u8, const SHARD_HEIGHT: u8>()
1933 -> ShardTree<MemoryShardStore<H, BlockHeight>, DEPTH, SHARD_HEIGHT>
1934 where
1935 H: Hashable + Clone + PartialEq,
1936 {
1937 ShardTree::new(
1938 MemoryShardStore::empty(),
1939 SHARDTREE_CHECKPOINT_ROLLING_WINDOW_SIZE as usize,
1940 )
1941 }
1942
1943 let mut shard_trees = ShardTrees {
1944 sapling: checkpoint_less(),
1945 orchard: checkpoint_less(),
1946 ironwood: checkpoint_less(),
1947 };
1948 assert_eq!(
1949 shard_trees
1950 .sapling
1951 .store()
1952 .max_checkpoint_id()
1953 .expect("infallible"),
1954 None
1955 );
1956
1957 let mut bytes = Vec::new();
1958 shard_trees.write(&mut bytes).expect("write should succeed");
1959 let roundtripped = ShardTrees::read(bytes.as_slice()).expect("read should succeed");
1960
1961 fn assert_initialization_checkpoint<S>(store: &S)
1962 where
1963 S: ShardStore<CheckpointId = BlockHeight, Error = std::convert::Infallible>,
1964 {
1965 assert_eq!(store.checkpoint_count().expect("infallible"), 1);
1966 assert_eq!(
1967 store.max_checkpoint_id().expect("infallible"),
1968 Some(BlockHeight::from_u32(0))
1969 );
1970 }
1971 assert_initialization_checkpoint(roundtripped.sapling.store());
1972 assert_initialization_checkpoint(roundtripped.orchard.store());
1973 assert_initialization_checkpoint(roundtripped.ironwood.store());
1974 }
1975
1976 #[test]
1980 fn shardtree_roundtrip_keeps_pinned_anchor_checkpoints() {
1981 use crate::shardtree_ext::ShardTreeExt as _;
1982
1983 let pinned = BlockHeight::from_u32(24);
1984 let mut shard_trees = ShardTrees::new();
1985
1986 shard_trees
1987 .sapling
1988 .store_mut()
1989 .add_retained_checkpoint(pinned)
1990 .expect("infallible");
1991 for height in 1..=150 {
1992 shard_trees
1993 .sapling
1994 .append_checkpoint(BlockHeight::from_u32(height))
1995 .expect("infallible");
1996 }
1997
1998 assert!(
1999 shard_trees
2000 .sapling
2001 .store()
2002 .get_checkpoint(&pinned)
2003 .expect("infallible")
2004 .is_some(),
2005 "pruning must not evict a pinned checkpoint"
2006 );
2007
2008 let mut bytes = Vec::new();
2009 shard_trees.write(&mut bytes).expect("write should succeed");
2010 let roundtripped = ShardTrees::read(bytes.as_slice()).expect("read should succeed");
2011
2012 let sapling_store = roundtripped.sapling.store();
2013 assert!(
2014 sapling_store
2015 .get_checkpoint(&pinned)
2016 .expect("infallible")
2017 .is_some(),
2018 "serialization must not evict a pinned checkpoint"
2019 );
2020 assert_eq!(
2021 sapling_store.checkpoint_count().expect("infallible"),
2022 SHARDTREE_CHECKPOINT_ROLLING_WINDOW_SIZE as usize + 1
2023 );
2024 assert!(
2025 sapling_store
2026 .retained_checkpoints()
2027 .expect("infallible")
2028 .is_empty()
2029 );
2030 }
2031}