zebra_state/service/finalized_state/disk_format/
transparent.rs

1//! Transparent transfer serialization formats for finalized data.
2//!
3//! # Correctness
4//!
5//! [`crate::constants::state_database_format_version_in_code()`] must be incremented
6//! each time the database format (column, serialization, etc) changes.
7
8use std::{cmp::max, fmt::Debug};
9
10use serde::{Deserialize, Serialize};
11
12use zebra_chain::{
13    amount::{self, Amount, NonNegative},
14    block::Height,
15    parameters::NetworkKind,
16    serialization::{ZcashDeserializeInto, ZcashSerialize},
17    transparent::{self, Address::*},
18};
19
20use crate::service::finalized_state::disk_format::{
21    block::{TransactionIndex, TransactionLocation, TRANSACTION_LOCATION_DISK_BYTES},
22    expand_zero_be_bytes, truncate_zero_be_bytes, FromDisk, IntoDisk,
23};
24
25#[cfg(any(test, feature = "proptest-impl"))]
26use proptest_derive::Arbitrary;
27
28#[cfg(any(test, feature = "proptest-impl"))]
29mod arbitrary;
30
31/// Transparent balances are stored as an 8 byte integer on disk.
32pub const BALANCE_DISK_BYTES: usize = 8;
33
34/// [`OutputIndex`]es are stored as 3 bytes on disk.
35///
36/// This reduces database size and increases lookup performance.
37pub const OUTPUT_INDEX_DISK_BYTES: usize = 3;
38
39/// The maximum value of an on-disk serialized [`OutputIndex`].
40///
41/// This allows us to store [`OutputLocation`]s in
42/// 8 bytes, which makes database searches more efficient.
43///
44/// # Consensus
45///
46/// This output index is impossible with the current 2 MB block size limit.
47///
48/// Since Zebra only stores fully verified blocks on disk, blocks with larger indexes
49/// are rejected before reaching the database.
50pub const MAX_ON_DISK_OUTPUT_INDEX: OutputIndex =
51    OutputIndex((1 << (OUTPUT_INDEX_DISK_BYTES * 8)) - 1);
52
53/// [`OutputLocation`]s are stored as a 3 byte height, 2 byte transaction index,
54/// and 3 byte output index on disk.
55///
56/// This reduces database size and increases lookup performance.
57pub const OUTPUT_LOCATION_DISK_BYTES: usize =
58    TRANSACTION_LOCATION_DISK_BYTES + OUTPUT_INDEX_DISK_BYTES;
59
60// Transparent types
61
62/// A transparent output's index in its transaction.
63#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
64pub struct OutputIndex(u32);
65
66impl OutputIndex {
67    /// Create a transparent output index from the Zcash consensus integer type.
68    ///
69    /// `u32` is also the inner type.
70    pub fn from_index(output_index: u32) -> OutputIndex {
71        OutputIndex(output_index)
72    }
73
74    /// Returns this index as the inner type.
75    pub fn index(&self) -> u32 {
76        self.0
77    }
78
79    /// Create a transparent output index from `usize`.
80    #[allow(dead_code)]
81    pub fn from_usize(output_index: usize) -> OutputIndex {
82        OutputIndex(
83            output_index
84                .try_into()
85                .expect("the maximum valid index fits in the inner type"),
86        )
87    }
88
89    /// Return this index as `usize`.
90    #[allow(dead_code)]
91    pub fn as_usize(&self) -> usize {
92        self.0
93            .try_into()
94            .expect("the maximum valid index fits in usize")
95    }
96
97    /// Create a transparent output index from `u64`.
98    #[allow(dead_code)]
99    pub fn from_u64(output_index: u64) -> OutputIndex {
100        OutputIndex(
101            output_index
102                .try_into()
103                .expect("the maximum u64 index fits in the inner type"),
104        )
105    }
106
107    /// Return this index as `u64`.
108    #[allow(dead_code)]
109    pub fn as_u64(&self) -> u64 {
110        self.0.into()
111    }
112}
113
114/// A transparent output's location in the chain, by block height and transaction index.
115///
116/// [`OutputLocation`]s are sorted in increasing chain order, by height, transaction index,
117/// and output index.
118#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
119#[cfg_attr(
120    any(test, feature = "proptest-impl"),
121    derive(Arbitrary, Serialize, Deserialize)
122)]
123pub struct OutputLocation {
124    /// The location of the transparent input's transaction.
125    transaction_location: TransactionLocation,
126
127    /// The index of the transparent output in its transaction.
128    output_index: OutputIndex,
129}
130
131impl OutputLocation {
132    /// Creates an output location from a block height, and `usize` transaction and output indexes.
133    #[allow(dead_code)]
134    pub fn from_usize(
135        height: Height,
136        transaction_index: usize,
137        output_index: usize,
138    ) -> OutputLocation {
139        OutputLocation {
140            transaction_location: TransactionLocation::from_usize(height, transaction_index),
141            output_index: OutputIndex::from_usize(output_index),
142        }
143    }
144
145    /// Creates an output location from an [`transparent::OutPoint`],
146    /// and the [`TransactionLocation`] of its transaction.
147    ///
148    /// The [`TransactionLocation`] is provided separately,
149    /// because the lookup is a database operation.
150    pub fn from_outpoint(
151        transaction_location: TransactionLocation,
152        outpoint: &transparent::OutPoint,
153    ) -> OutputLocation {
154        OutputLocation::from_output_index(transaction_location, outpoint.index)
155    }
156
157    /// Creates an output location from a [`TransactionLocation`] and a `u32` output index.
158    ///
159    /// Output indexes are serialized to `u32` in the Zcash consensus-critical transaction format.
160    pub fn from_output_index(
161        transaction_location: TransactionLocation,
162        output_index: u32,
163    ) -> OutputLocation {
164        OutputLocation {
165            transaction_location,
166            output_index: OutputIndex::from_index(output_index),
167        }
168    }
169
170    /// Returns the height of this [`transparent::Output`].
171    pub fn height(&self) -> Height {
172        self.transaction_location.height
173    }
174
175    /// Returns the transaction index of this [`transparent::Output`].
176    pub fn transaction_index(&self) -> TransactionIndex {
177        self.transaction_location.index
178    }
179
180    /// Returns the output index of this [`transparent::Output`].
181    pub fn output_index(&self) -> OutputIndex {
182        self.output_index
183    }
184
185    /// Returns the location of the transaction for this [`transparent::Output`].
186    pub fn transaction_location(&self) -> TransactionLocation {
187        self.transaction_location
188    }
189
190    /// Allows tests to set the height of this output location.
191    #[cfg(any(test, feature = "proptest-impl"))]
192    #[allow(dead_code)]
193    pub fn height_mut(&mut self) -> &mut Height {
194        &mut self.transaction_location.height
195    }
196}
197
198/// The location of the first [`transparent::Output`] sent to an address.
199///
200/// The address location stays the same, even if the corresponding output
201/// has been spent.
202///
203/// The first output location is used to represent the address in the database,
204/// because output locations are significantly smaller than addresses.
205///
206/// TODO: make this a different type to OutputLocation?
207///       derive IntoDisk and FromDisk?
208pub type AddressLocation = OutputLocation;
209
210/// Data which Zebra indexes for each [`transparent::Address`].
211///
212/// Currently, Zebra tracks this data 1:1 for each address:
213/// - the balance [`Amount`] for a transparent address, and
214/// - the [`AddressLocation`] for the first [`transparent::Output`] sent to that address
215///   (regardless of whether that output is spent or unspent).
216///
217/// All other address data is tracked multiple times for each address
218/// (UTXOs and transactions).
219#[derive(Copy, Clone, Debug, Eq, PartialEq)]
220#[cfg_attr(
221    any(test, feature = "proptest-impl"),
222    derive(Arbitrary, Serialize, Deserialize)
223)]
224pub struct AddressBalanceLocation {
225    /// The total balance of all UTXOs sent to an address.
226    balance: Amount<NonNegative>,
227
228    /// The location of the first [`transparent::Output`] sent to an address.
229    location: AddressLocation,
230}
231
232impl AddressBalanceLocation {
233    /// Creates a new [`AddressBalanceLocation`] from the location of
234    /// the first [`transparent::Output`] sent to an address.
235    ///
236    /// The returned value has a zero initial balance.
237    pub fn new(first_output: OutputLocation) -> AddressBalanceLocation {
238        AddressBalanceLocation {
239            balance: Amount::zero(),
240            location: first_output,
241        }
242    }
243
244    /// Returns the current balance for the address.
245    pub fn balance(&self) -> Amount<NonNegative> {
246        self.balance
247    }
248
249    /// Returns a mutable reference to the current balance for the address.
250    pub fn balance_mut(&mut self) -> &mut Amount<NonNegative> {
251        &mut self.balance
252    }
253
254    /// Updates the current balance by adding the supplied output's value.
255    pub fn receive_output(
256        &mut self,
257        unspent_output: &transparent::Output,
258    ) -> Result<(), amount::Error> {
259        self.balance = (self.balance + unspent_output.value())?;
260
261        Ok(())
262    }
263
264    /// Updates the current balance by subtracting the supplied output's value.
265    pub fn spend_output(
266        &mut self,
267        spent_output: &transparent::Output,
268    ) -> Result<(), amount::Error> {
269        self.balance = (self.balance - spent_output.value())?;
270
271        Ok(())
272    }
273
274    /// Returns the location of the first [`transparent::Output`] sent to an address.
275    pub fn address_location(&self) -> AddressLocation {
276        self.location
277    }
278
279    /// Allows tests to set the height of the address location.
280    #[cfg(any(test, feature = "proptest-impl"))]
281    #[allow(dead_code)]
282    pub fn height_mut(&mut self) -> &mut Height {
283        &mut self.location.transaction_location.height
284    }
285}
286
287/// A single unspent output for a [`transparent::Address`].
288///
289/// We store both the address location key and unspend output location value
290/// in the RocksDB column family key. This improves insert and delete performance.
291///
292/// This requires 8 extra bytes for each unspent output,
293/// because we repeat the key for each value.
294/// But RocksDB compression reduces the duplicate data size on disk.
295#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
296#[cfg_attr(
297    any(test, feature = "proptest-impl"),
298    derive(Arbitrary, Serialize, Deserialize)
299)]
300pub struct AddressUnspentOutput {
301    /// The location of the first [`transparent::Output`] sent to the address in `output`.
302    address_location: AddressLocation,
303
304    /// The location of this unspent output.
305    unspent_output_location: OutputLocation,
306}
307
308impl AddressUnspentOutput {
309    /// Create a new [`AddressUnspentOutput`] from an address location,
310    /// and an unspent output location.
311    pub fn new(
312        address_location: AddressLocation,
313        unspent_output_location: OutputLocation,
314    ) -> AddressUnspentOutput {
315        AddressUnspentOutput {
316            address_location,
317            unspent_output_location,
318        }
319    }
320
321    /// Create an [`AddressUnspentOutput`] which starts iteration for the
322    /// supplied address. Used to look up the first output with
323    /// [`ReadDisk::zs_next_key_value_from`][1].
324    ///
325    /// The unspent output location is before all unspent output locations in
326    /// the index. It is always invalid, due to the genesis consensus rules. But
327    /// this is not an issue since [`ReadDisk::zs_next_key_value_from`][1] will
328    /// fetch the next existing (valid) value.
329    ///
330    /// [1]: super::super::disk_db::ReadDisk::zs_next_key_value_from
331    pub fn address_iterator_start(address_location: AddressLocation) -> AddressUnspentOutput {
332        // Iterating from the lowest possible output location gets us the first output.
333        let zero_output_location = OutputLocation::from_usize(Height(0), 0, 0);
334
335        AddressUnspentOutput {
336            address_location,
337            unspent_output_location: zero_output_location,
338        }
339    }
340
341    /// Update the unspent output location to the next possible output for the
342    /// supplied address. Used to look up the next output with
343    /// [`ReadDisk::zs_next_key_value_from`][1].
344    ///
345    /// The updated unspent output location may be invalid, which is not an
346    /// issue since [`ReadDisk::zs_next_key_value_from`][1] will fetch the next
347    /// existing (valid) value.
348    ///
349    /// [1]: super::super::disk_db::ReadDisk::zs_next_key_value_from
350    pub fn address_iterator_next(&mut self) {
351        // Iterating from the next possible output location gets us the next output,
352        // even if it is in a later block or transaction.
353        //
354        // Consensus: the block size limit is 2MB, which is much lower than the index range.
355        self.unspent_output_location.output_index.0 += 1;
356    }
357
358    /// The location of the first [`transparent::Output`] sent to the address of this output.
359    ///
360    /// This can be used to look up the address.
361    pub fn address_location(&self) -> AddressLocation {
362        self.address_location
363    }
364
365    /// The location of this unspent output.
366    pub fn unspent_output_location(&self) -> OutputLocation {
367        self.unspent_output_location
368    }
369
370    /// Allows tests to modify the address location.
371    #[cfg(any(test, feature = "proptest-impl"))]
372    #[allow(dead_code)]
373    pub fn address_location_mut(&mut self) -> &mut AddressLocation {
374        &mut self.address_location
375    }
376
377    /// Allows tests to modify the unspent output location.
378    #[cfg(any(test, feature = "proptest-impl"))]
379    #[allow(dead_code)]
380    pub fn unspent_output_location_mut(&mut self) -> &mut OutputLocation {
381        &mut self.unspent_output_location
382    }
383}
384
385/// A single transaction sent to a [`transparent::Address`].
386///
387/// We store both the address location key and transaction location value
388/// in the RocksDB column family key. This improves insert and delete performance.
389///
390/// This requires 8 extra bytes for each transaction location,
391/// because we repeat the key for each value.
392/// But RocksDB compression reduces the duplicate data size on disk.
393#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
394#[cfg_attr(
395    any(test, feature = "proptest-impl"),
396    derive(Arbitrary, Serialize, Deserialize)
397)]
398pub struct AddressTransaction {
399    /// The location of the first [`transparent::Output`] sent to the address in `output`.
400    address_location: AddressLocation,
401
402    /// The location of the transaction sent to the address.
403    transaction_location: TransactionLocation,
404}
405
406impl AddressTransaction {
407    /// Create a new [`AddressTransaction`] from an address location,
408    /// and a transaction location.
409    pub fn new(
410        address_location: AddressLocation,
411        transaction_location: TransactionLocation,
412    ) -> AddressTransaction {
413        AddressTransaction {
414            address_location,
415            transaction_location,
416        }
417    }
418
419    /// Create a range of [`AddressTransaction`]s which starts iteration for the supplied
420    /// address. Starts at the first UTXO, or at the `query` start height, whichever is greater.
421    /// Ends at the maximum possible transaction index for the end height.
422    ///
423    /// Used to look up transactions with [`DiskDb::zs_forward_range_iter`][1].
424    ///
425    /// The transaction locations in the:
426    /// - start bound might be invalid, if it is based on the `query` start height.
427    /// - end bound will always be invalid.
428    ///
429    /// But this is not an issue, since [`DiskDb::zs_forward_range_iter`][1] will fetch all existing
430    /// (valid) values in the range.
431    ///
432    /// [1]: super::super::disk_db::DiskDb
433    pub fn address_iterator_range(
434        address_location: AddressLocation,
435        query: std::ops::RangeInclusive<Height>,
436    ) -> std::ops::RangeInclusive<AddressTransaction> {
437        // Iterating from the lowest possible transaction location gets us the first transaction.
438        //
439        // The address location is the output location of the first UTXO sent to the address,
440        // and addresses can not spend funds until they receive their first UTXO.
441        let first_utxo_location = address_location.transaction_location();
442
443        // Iterating from the start height to the end height filters out transactions that aren't needed.
444        let query_start_location = TransactionLocation::from_index(*query.start(), 0);
445        let query_end_location = TransactionLocation::from_index(*query.end(), u16::MAX);
446
447        let addr_tx = |tx_loc| AddressTransaction::new(address_location, tx_loc);
448
449        addr_tx(max(first_utxo_location, query_start_location))..=addr_tx(query_end_location)
450    }
451
452    /// Update the transaction location to the next possible transaction for the
453    /// supplied address. Used to look up the next output with
454    /// [`ReadDisk::zs_next_key_value_from`][1].
455    ///
456    /// The updated transaction location may be invalid, which is not an issue
457    /// since [`ReadDisk::zs_next_key_value_from`][1] will fetch the next
458    /// existing (valid) value.
459    ///
460    /// [1]: super::super::disk_db::ReadDisk::zs_next_key_value_from
461    #[allow(dead_code)]
462    pub fn address_iterator_next(&mut self) {
463        // Iterating from the next possible output location gets us the next output,
464        // even if it is in a later block or transaction.
465        //
466        // Consensus: the block size limit is 2MB, which is much lower than the index range.
467        self.transaction_location.index.0 += 1;
468    }
469
470    /// The location of the first [`transparent::Output`] sent to the address of this output.
471    ///
472    /// This can be used to look up the address.
473    pub fn address_location(&self) -> AddressLocation {
474        self.address_location
475    }
476
477    /// The location of this transaction.
478    pub fn transaction_location(&self) -> TransactionLocation {
479        self.transaction_location
480    }
481
482    /// Allows tests to modify the address location.
483    #[cfg(any(test, feature = "proptest-impl"))]
484    #[allow(dead_code)]
485    pub fn address_location_mut(&mut self) -> &mut AddressLocation {
486        &mut self.address_location
487    }
488
489    /// Allows tests to modify the unspent output location.
490    #[cfg(any(test, feature = "proptest-impl"))]
491    #[allow(dead_code)]
492    pub fn transaction_location_mut(&mut self) -> &mut TransactionLocation {
493        &mut self.transaction_location
494    }
495}
496
497// Transparent trait impls
498
499/// Returns a byte representing the [`transparent::Address`] variant.
500fn address_variant(address: &transparent::Address) -> u8 {
501    use NetworkKind::*;
502    // Return smaller values for more common variants.
503    //
504    // (This probably doesn't matter, but it might help slightly with data compression.)
505    match (address.network_kind(), address) {
506        (Mainnet, PayToPublicKeyHash { .. }) => 0,
507        (Mainnet, PayToScriptHash { .. }) => 1,
508        // There's no way to distinguish between Regtest and Testnet for encoded transparent addresses,
509        // we can consider `Regtest` to use `Testnet` transparent addresses, so it's okay to use the `Testnet`
510        // address variant for `Regtest` transparent addresses in the db format
511        (Testnet | Regtest, PayToPublicKeyHash { .. }) => 2,
512        (Testnet | Regtest, PayToScriptHash { .. }) => 3,
513    }
514}
515
516impl IntoDisk for transparent::Address {
517    type Bytes = [u8; 21];
518
519    fn as_bytes(&self) -> Self::Bytes {
520        let variant_bytes = vec![address_variant(self)];
521        let hash_bytes = self.hash_bytes().to_vec();
522
523        [variant_bytes, hash_bytes].concat().try_into().unwrap()
524    }
525}
526
527#[cfg(any(test, feature = "proptest-impl"))]
528impl FromDisk for transparent::Address {
529    fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
530        let (address_variant, hash_bytes) = disk_bytes.as_ref().split_at(1);
531
532        let address_variant = address_variant[0];
533        let hash_bytes = hash_bytes.try_into().unwrap();
534
535        let network = if address_variant < 2 {
536            NetworkKind::Mainnet
537        } else {
538            NetworkKind::Testnet
539        };
540
541        if address_variant % 2 == 0 {
542            transparent::Address::from_pub_key_hash(network, hash_bytes)
543        } else {
544            transparent::Address::from_script_hash(network, hash_bytes)
545        }
546    }
547}
548
549impl IntoDisk for Amount<NonNegative> {
550    type Bytes = [u8; BALANCE_DISK_BYTES];
551
552    fn as_bytes(&self) -> Self::Bytes {
553        self.to_bytes()
554    }
555}
556
557impl FromDisk for Amount<NonNegative> {
558    fn from_bytes(bytes: impl AsRef<[u8]>) -> Self {
559        let array = bytes.as_ref().try_into().unwrap();
560        Amount::from_bytes(array).unwrap()
561    }
562}
563
564impl IntoDisk for OutputIndex {
565    type Bytes = [u8; OUTPUT_INDEX_DISK_BYTES];
566
567    fn as_bytes(&self) -> Self::Bytes {
568        let mem_bytes = self.index().to_be_bytes();
569
570        let disk_bytes = truncate_zero_be_bytes(&mem_bytes, OUTPUT_INDEX_DISK_BYTES);
571
572        match disk_bytes {
573            Some(b) => b.try_into().unwrap(),
574            // # Security
575            //
576            // The RPC method or state query was given a transparent output index that is
577            // impossible with the current block size limit of 2 MB. To save space in database
578            // indexes, we don't support output indexes 2^24 and above.
579            //
580            // Instead,  we return an invalid database output index to the lookup code,
581            // which can never be inserted into the database as part of a valid block.
582            // So RPC methods will return an error or None.
583            None => {
584                #[cfg(test)]
585                {
586                    use zebra_chain::serialization::TrustedPreallocate;
587                    assert!(
588                        u64::from(MAX_ON_DISK_OUTPUT_INDEX.0)
589                            > zebra_chain::transparent::Output::max_allocation(),
590                        "increased block size requires database output index format change",
591                    );
592                }
593
594                truncate_zero_be_bytes(
595                    &MAX_ON_DISK_OUTPUT_INDEX.0.to_be_bytes(),
596                    OUTPUT_INDEX_DISK_BYTES,
597                )
598                .expect("max on disk output index is valid")
599                .try_into()
600                .unwrap()
601            }
602        }
603    }
604}
605
606impl FromDisk for OutputIndex {
607    fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
608        let mem_len = u32::BITS / 8;
609        let mem_len = mem_len.try_into().unwrap();
610
611        let mem_bytes = expand_zero_be_bytes(disk_bytes.as_ref(), mem_len);
612        let mem_bytes = mem_bytes.try_into().unwrap();
613        OutputIndex::from_index(u32::from_be_bytes(mem_bytes))
614    }
615}
616
617impl IntoDisk for OutputLocation {
618    type Bytes = [u8; OUTPUT_LOCATION_DISK_BYTES];
619
620    fn as_bytes(&self) -> Self::Bytes {
621        let transaction_location_bytes = self.transaction_location().as_bytes().to_vec();
622        let output_index_bytes = self.output_index().as_bytes().to_vec();
623
624        [transaction_location_bytes, output_index_bytes]
625            .concat()
626            .try_into()
627            .unwrap()
628    }
629}
630
631impl FromDisk for OutputLocation {
632    fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
633        let (transaction_location_bytes, output_index_bytes) = disk_bytes
634            .as_ref()
635            .split_at(TRANSACTION_LOCATION_DISK_BYTES);
636
637        let transaction_location = TransactionLocation::from_bytes(transaction_location_bytes);
638        let output_index = OutputIndex::from_bytes(output_index_bytes);
639
640        OutputLocation {
641            transaction_location,
642            output_index,
643        }
644    }
645}
646
647impl IntoDisk for AddressBalanceLocation {
648    type Bytes = [u8; BALANCE_DISK_BYTES + OUTPUT_LOCATION_DISK_BYTES];
649
650    fn as_bytes(&self) -> Self::Bytes {
651        let balance_bytes = self.balance().as_bytes().to_vec();
652        let address_location_bytes = self.address_location().as_bytes().to_vec();
653
654        [balance_bytes, address_location_bytes]
655            .concat()
656            .try_into()
657            .unwrap()
658    }
659}
660
661impl FromDisk for AddressBalanceLocation {
662    fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
663        let (balance_bytes, address_location_bytes) =
664            disk_bytes.as_ref().split_at(BALANCE_DISK_BYTES);
665
666        let balance = Amount::from_bytes(balance_bytes.try_into().unwrap()).unwrap();
667        let address_location = AddressLocation::from_bytes(address_location_bytes);
668
669        let mut address_balance_location = AddressBalanceLocation::new(address_location);
670        *address_balance_location.balance_mut() = balance;
671
672        address_balance_location
673    }
674}
675
676impl IntoDisk for transparent::Output {
677    type Bytes = Vec<u8>;
678
679    fn as_bytes(&self) -> Self::Bytes {
680        self.zcash_serialize_to_vec().unwrap()
681    }
682}
683
684impl FromDisk for transparent::Output {
685    fn from_bytes(bytes: impl AsRef<[u8]>) -> Self {
686        bytes.as_ref().zcash_deserialize_into().unwrap()
687    }
688}
689
690impl IntoDisk for AddressUnspentOutput {
691    type Bytes = [u8; OUTPUT_LOCATION_DISK_BYTES + OUTPUT_LOCATION_DISK_BYTES];
692
693    fn as_bytes(&self) -> Self::Bytes {
694        let address_location_bytes = self.address_location().as_bytes();
695        let unspent_output_location_bytes = self.unspent_output_location().as_bytes();
696
697        [address_location_bytes, unspent_output_location_bytes]
698            .concat()
699            .try_into()
700            .unwrap()
701    }
702}
703
704impl FromDisk for AddressUnspentOutput {
705    fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
706        let (address_location_bytes, unspent_output_location_bytes) =
707            disk_bytes.as_ref().split_at(OUTPUT_LOCATION_DISK_BYTES);
708
709        let address_location = AddressLocation::from_bytes(address_location_bytes);
710        let unspent_output_location = AddressLocation::from_bytes(unspent_output_location_bytes);
711
712        AddressUnspentOutput::new(address_location, unspent_output_location)
713    }
714}
715
716impl IntoDisk for AddressTransaction {
717    type Bytes = [u8; OUTPUT_LOCATION_DISK_BYTES + TRANSACTION_LOCATION_DISK_BYTES];
718
719    fn as_bytes(&self) -> Self::Bytes {
720        let address_location_bytes: [u8; OUTPUT_LOCATION_DISK_BYTES] =
721            self.address_location().as_bytes();
722        let transaction_location_bytes: [u8; TRANSACTION_LOCATION_DISK_BYTES] =
723            self.transaction_location().as_bytes();
724
725        address_location_bytes
726            .iter()
727            .copied()
728            .chain(transaction_location_bytes.iter().copied())
729            .collect::<Vec<u8>>()
730            .try_into()
731            .expect("concatenation of fixed-sized arrays should have the correct size")
732    }
733}
734
735impl FromDisk for AddressTransaction {
736    fn from_bytes(disk_bytes: impl AsRef<[u8]>) -> Self {
737        let (address_location_bytes, transaction_location_bytes) =
738            disk_bytes.as_ref().split_at(OUTPUT_LOCATION_DISK_BYTES);
739
740        let address_location = AddressLocation::from_bytes(address_location_bytes);
741        let transaction_location = TransactionLocation::from_bytes(transaction_location_bytes);
742
743        AddressTransaction::new(address_location, transaction_location)
744    }
745}