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