zebra_chain/
value_balance.rs

1//! Balances in chain value pools and transaction value pools.
2
3use crate::amount::{self, Amount, Constraint, NegativeAllowed, NonNegative};
4
5use core::fmt;
6
7#[cfg(any(test, feature = "proptest-impl"))]
8use std::{borrow::Borrow, collections::HashMap};
9
10#[cfg(any(test, feature = "proptest-impl"))]
11use crate::{amount::MAX_MONEY, transaction::Transaction, transparent};
12
13#[cfg(any(test, feature = "proptest-impl"))]
14mod arbitrary;
15
16#[cfg(test)]
17mod tests;
18
19use ValueBalanceError::*;
20
21/// A balance in each chain value pool or transaction value pool.
22#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
23pub struct ValueBalance<C> {
24    transparent: Amount<C>,
25    sprout: Amount<C>,
26    sapling: Amount<C>,
27    orchard: Amount<C>,
28    deferred: Amount<C>,
29}
30
31impl<C> ValueBalance<C>
32where
33    C: Constraint + Copy,
34{
35    /// Creates a [`ValueBalance`] from the given transparent amount.
36    pub fn from_transparent_amount(transparent_amount: Amount<C>) -> Self {
37        ValueBalance {
38            transparent: transparent_amount,
39            ..ValueBalance::zero()
40        }
41    }
42
43    /// Creates a [`ValueBalance`] from the given sprout amount.
44    pub fn from_sprout_amount(sprout_amount: Amount<C>) -> Self {
45        ValueBalance {
46            sprout: sprout_amount,
47            ..ValueBalance::zero()
48        }
49    }
50
51    /// Creates a [`ValueBalance`] from the given sapling amount.
52    pub fn from_sapling_amount(sapling_amount: Amount<C>) -> Self {
53        ValueBalance {
54            sapling: sapling_amount,
55            ..ValueBalance::zero()
56        }
57    }
58
59    /// Creates a [`ValueBalance`] from the given orchard amount.
60    pub fn from_orchard_amount(orchard_amount: Amount<C>) -> Self {
61        ValueBalance {
62            orchard: orchard_amount,
63            ..ValueBalance::zero()
64        }
65    }
66
67    /// Get the transparent amount from the [`ValueBalance`].
68    pub fn transparent_amount(&self) -> Amount<C> {
69        self.transparent
70    }
71
72    /// Insert a transparent value balance into a given [`ValueBalance`]
73    /// leaving the other values untouched.
74    pub fn set_transparent_value_balance(
75        &mut self,
76        transparent_value_balance: ValueBalance<C>,
77    ) -> &Self {
78        self.transparent = transparent_value_balance.transparent;
79        self
80    }
81
82    /// Get the sprout amount from the [`ValueBalance`].
83    pub fn sprout_amount(&self) -> Amount<C> {
84        self.sprout
85    }
86
87    /// Insert a sprout value balance into a given [`ValueBalance`]
88    /// leaving the other values untouched.
89    pub fn set_sprout_value_balance(&mut self, sprout_value_balance: ValueBalance<C>) -> &Self {
90        self.sprout = sprout_value_balance.sprout;
91        self
92    }
93
94    /// Get the sapling amount from the [`ValueBalance`].
95    pub fn sapling_amount(&self) -> Amount<C> {
96        self.sapling
97    }
98
99    /// Insert a sapling value balance into a given [`ValueBalance`]
100    /// leaving the other values untouched.
101    pub fn set_sapling_value_balance(&mut self, sapling_value_balance: ValueBalance<C>) -> &Self {
102        self.sapling = sapling_value_balance.sapling;
103        self
104    }
105
106    /// Get the orchard amount from the [`ValueBalance`].
107    pub fn orchard_amount(&self) -> Amount<C> {
108        self.orchard
109    }
110
111    /// Insert an orchard value balance into a given [`ValueBalance`]
112    /// leaving the other values untouched.
113    pub fn set_orchard_value_balance(&mut self, orchard_value_balance: ValueBalance<C>) -> &Self {
114        self.orchard = orchard_value_balance.orchard;
115        self
116    }
117
118    /// Returns the deferred amount.
119    pub fn deferred_amount(&self) -> Amount<C> {
120        self.deferred
121    }
122
123    /// Sets the deferred amount without affecting other amounts.
124    pub fn set_deferred_amount(&mut self, deferred_amount: Amount<C>) -> &Self {
125        self.deferred = deferred_amount;
126        self
127    }
128
129    /// Creates a [`ValueBalance`] where all the pools are zero.
130    pub fn zero() -> Self {
131        let zero = Amount::zero();
132        Self {
133            transparent: zero,
134            sprout: zero,
135            sapling: zero,
136            orchard: zero,
137            deferred: zero,
138        }
139    }
140
141    /// Convert this value balance to a different ValueBalance type,
142    /// if it satisfies the new constraint
143    pub fn constrain<C2>(self) -> Result<ValueBalance<C2>, ValueBalanceError>
144    where
145        C2: Constraint,
146    {
147        Ok(ValueBalance::<C2> {
148            transparent: self.transparent.constrain().map_err(Transparent)?,
149            sprout: self.sprout.constrain().map_err(Sprout)?,
150            sapling: self.sapling.constrain().map_err(Sapling)?,
151            orchard: self.orchard.constrain().map_err(Orchard)?,
152            deferred: self.deferred.constrain().map_err(Deferred)?,
153        })
154    }
155}
156
157impl ValueBalance<NegativeAllowed> {
158    /// Assumes that this value balance is a non-coinbase transaction value balance,
159    /// and returns the remaining value in the transaction value pool.
160    ///
161    /// # Consensus
162    ///
163    /// > The remaining value in the transparent transaction value pool MUST be nonnegative.
164    ///
165    /// <https://zips.z.cash/protocol/protocol.pdf#transactions>
166    ///
167    /// This rule applies to Block and Mempool transactions.
168    ///
169    /// Design: <https://github.com/ZcashFoundation/zebra/blob/main/book/src/dev/rfcs/0012-value-pools.md#definitions>
170    pub fn remaining_transaction_value(&self) -> Result<Amount<NonNegative>, amount::Error> {
171        // Calculated by summing the transparent, sprout, sapling, and orchard value balances,
172        // as specified in:
173        // https://zebra.zfnd.org/dev/rfcs/0012-value-pools.html#definitions
174        //
175        // This will error if the remaining value in the transaction value pool is negative.
176        (self.transparent + self.sprout + self.sapling + self.orchard)?.constrain::<NonNegative>()
177    }
178}
179
180impl ValueBalance<NonNegative> {
181    /// Returns the sum of this value balance, and the chain value pool changes in `transaction`.
182    ///
183    /// `outputs` must contain the [`transparent::Output`]s of every input in this transaction,
184    /// including UTXOs created by earlier transactions in its block.
185    ///
186    /// Note: the chain value pool has the opposite sign to the transaction
187    /// value pool.
188    ///
189    /// # Consensus
190    ///
191    /// > If any of the "Sprout chain value pool balance", "Sapling chain value pool balance", or
192    /// > "Orchard chain value pool balance" would become negative in the block chain created
193    /// > as a result of accepting a block, then all nodes MUST reject the block as invalid.
194    /// >
195    /// > Nodes MAY relay transactions even if one or more of them cannot be mined due to the
196    /// > aforementioned restriction.
197    ///
198    /// <https://zips.z.cash/zip-0209#specification>
199    ///
200    /// Since this consensus rule is optional for mempool transactions,
201    /// Zebra does not check it in the mempool transaction verifier.
202    #[cfg(any(test, feature = "proptest-impl"))]
203    pub fn add_transaction(
204        self,
205        transaction: impl Borrow<Transaction>,
206        utxos: &HashMap<transparent::OutPoint, transparent::Output>,
207    ) -> Result<ValueBalance<NonNegative>, ValueBalanceError> {
208        use std::ops::Neg;
209
210        // the chain pool (unspent outputs) has the opposite sign to
211        // transaction value balances (inputs - outputs)
212        let chain_value_pool_change = transaction
213            .borrow()
214            .value_balance_from_outputs(utxos)?
215            .neg();
216
217        self.add_chain_value_pool_change(chain_value_pool_change)
218    }
219
220    /// Returns the sum of this value balance, and the chain value pool change in `input`.
221    ///
222    /// `outputs` must contain the [`transparent::Output`] spent by `input`,
223    /// (including UTXOs created by earlier transactions in its block).
224    ///
225    /// Note: the chain value pool has the opposite sign to the transaction
226    /// value pool. Inputs remove value from the chain value pool.
227    #[cfg(any(test, feature = "proptest-impl"))]
228    pub fn add_transparent_input(
229        self,
230        input: impl Borrow<transparent::Input>,
231        utxos: &HashMap<transparent::OutPoint, transparent::Output>,
232    ) -> Result<ValueBalance<NonNegative>, ValueBalanceError> {
233        use std::ops::Neg;
234
235        // the chain pool (unspent outputs) has the opposite sign to
236        // transaction value balances (inputs - outputs)
237        let transparent_value_pool_change = input.borrow().value_from_outputs(utxos).neg();
238        let transparent_value_pool_change =
239            ValueBalance::from_transparent_amount(transparent_value_pool_change);
240
241        self.add_chain_value_pool_change(transparent_value_pool_change)
242    }
243
244    /// Returns the sum of this value balance, and the given `chain_value_pool_change`.
245    ///
246    /// Note that the chain value pool has the opposite sign to the transaction value pool.
247    ///
248    /// # Consensus
249    ///
250    /// > If the Sprout chain value pool balance would become negative in the block chain
251    /// > created as a result of accepting a block, then all nodes MUST reject the block as invalid.
252    ///
253    /// <https://zips.z.cash/protocol/protocol.pdf#joinsplitbalance>
254    ///
255    /// > If the Sapling chain value pool balance would become negative in the block chain
256    /// > created as a result of accepting a block, then all nodes MUST reject the block as invalid.
257    ///
258    /// <https://zips.z.cash/protocol/protocol.pdf#saplingbalance>
259    ///
260    /// > If the Orchard chain value pool balance would become negative in the block chain
261    /// > created as a result of accepting a block , then all nodes MUST reject the block as invalid.
262    ///
263    /// <https://zips.z.cash/protocol/protocol.pdf#orchardbalance>
264    ///
265    /// > If any of the "Sprout chain value pool balance", "Sapling chain value pool balance", or
266    /// > "Orchard chain value pool balance" would become negative in the block chain created
267    /// > as a result of accepting a block, then all nodes MUST reject the block as invalid.
268    ///
269    /// <https://zips.z.cash/zip-0209#specification>
270    ///
271    /// Zebra also checks that the transparent value pool is non-negative.
272    /// In Zebra, we define this pool as the sum of all unspent transaction outputs.
273    /// (Despite their encoding as an `int64`, transparent output values must be non-negative.)
274    ///
275    /// This is a consensus rule derived from Bitcoin:
276    ///
277    /// > because a UTXO can only be spent once,
278    /// > the full value of the included UTXOs must be spent or given to a miner as a transaction fee.
279    ///
280    /// <https://developer.bitcoin.org/devguide/transactions.html#transaction-fees-and-change>
281    ///
282    /// We implement the consensus rules above by constraining the returned value balance to
283    /// [`ValueBalance<NonNegative>`].
284    #[allow(clippy::unwrap_in_result)]
285    pub fn add_chain_value_pool_change(
286        self,
287        chain_value_pool_change: ValueBalance<NegativeAllowed>,
288    ) -> Result<ValueBalance<NonNegative>, ValueBalanceError> {
289        let mut chain_value_pool = self
290            .constrain::<NegativeAllowed>()
291            .expect("conversion from NonNegative to NegativeAllowed is always valid");
292        chain_value_pool = (chain_value_pool + chain_value_pool_change)?;
293
294        chain_value_pool.constrain()
295    }
296
297    /// Create a fake value pool for testing purposes.
298    ///
299    /// The resulting [`ValueBalance`] will have half of the MAX_MONEY amount on each pool.
300    #[cfg(any(test, feature = "proptest-impl"))]
301    pub fn fake_populated_pool() -> ValueBalance<NonNegative> {
302        let mut fake_value_pool = ValueBalance::zero();
303
304        let fake_transparent_value_balance =
305            ValueBalance::from_transparent_amount(Amount::try_from(MAX_MONEY / 2).unwrap());
306        let fake_sprout_value_balance =
307            ValueBalance::from_sprout_amount(Amount::try_from(MAX_MONEY / 2).unwrap());
308        let fake_sapling_value_balance =
309            ValueBalance::from_sapling_amount(Amount::try_from(MAX_MONEY / 2).unwrap());
310        let fake_orchard_value_balance =
311            ValueBalance::from_orchard_amount(Amount::try_from(MAX_MONEY / 2).unwrap());
312
313        fake_value_pool.set_transparent_value_balance(fake_transparent_value_balance);
314        fake_value_pool.set_sprout_value_balance(fake_sprout_value_balance);
315        fake_value_pool.set_sapling_value_balance(fake_sapling_value_balance);
316        fake_value_pool.set_orchard_value_balance(fake_orchard_value_balance);
317
318        fake_value_pool
319    }
320
321    /// To byte array
322    pub fn to_bytes(self) -> [u8; 40] {
323        match [
324            self.transparent.to_bytes(),
325            self.sprout.to_bytes(),
326            self.sapling.to_bytes(),
327            self.orchard.to_bytes(),
328            self.deferred.to_bytes(),
329        ]
330        .concat()
331        .try_into()
332        {
333            Ok(bytes) => bytes,
334            _ => unreachable!(
335                "five [u8; 8] should always concat with no error into a single [u8; 40]"
336            ),
337        }
338    }
339
340    /// From byte array
341    #[allow(clippy::unwrap_in_result)]
342    pub fn from_bytes(bytes: &[u8]) -> Result<ValueBalance<NonNegative>, ValueBalanceError> {
343        let bytes_length = bytes.len();
344
345        // Return an error early if bytes don't have the right length instead of panicking later.
346        match bytes_length {
347            32 | 40 => {}
348            _ => return Err(Unparsable),
349        };
350
351        let transparent = Amount::from_bytes(
352            bytes[0..8]
353                .try_into()
354                .expect("transparent amount should be parsable"),
355        )
356        .map_err(Transparent)?;
357
358        let sprout = Amount::from_bytes(
359            bytes[8..16]
360                .try_into()
361                .expect("sprout amount should be parsable"),
362        )
363        .map_err(Sprout)?;
364
365        let sapling = Amount::from_bytes(
366            bytes[16..24]
367                .try_into()
368                .expect("sapling amount should be parsable"),
369        )
370        .map_err(Sapling)?;
371
372        let orchard = Amount::from_bytes(
373            bytes[24..32]
374                .try_into()
375                .expect("orchard amount should be parsable"),
376        )
377        .map_err(Orchard)?;
378
379        let deferred = match bytes_length {
380            32 => Amount::zero(),
381            40 => Amount::from_bytes(
382                bytes[32..40]
383                    .try_into()
384                    .expect("deferred amount should be parsable"),
385            )
386            .map_err(Deferred)?,
387            _ => return Err(Unparsable),
388        };
389
390        Ok(ValueBalance {
391            transparent,
392            sprout,
393            sapling,
394            orchard,
395            deferred,
396        })
397    }
398}
399
400#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
401/// Errors that can be returned when validating a [`ValueBalance`]
402pub enum ValueBalanceError {
403    /// transparent amount error {0}
404    Transparent(amount::Error),
405
406    /// sprout amount error {0}
407    Sprout(amount::Error),
408
409    /// sapling amount error {0}
410    Sapling(amount::Error),
411
412    /// orchard amount error {0}
413    Orchard(amount::Error),
414
415    /// deferred amount error {0}
416    Deferred(amount::Error),
417
418    /// ValueBalance is unparsable
419    Unparsable,
420}
421
422impl fmt::Display for ValueBalanceError {
423    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
424        f.write_str(&match self {
425            Transparent(e) => format!("transparent amount err: {e}"),
426            Sprout(e) => format!("sprout amount err: {e}"),
427            Sapling(e) => format!("sapling amount err: {e}"),
428            Orchard(e) => format!("orchard amount err: {e}"),
429            Deferred(e) => format!("deferred amount err: {e}"),
430            Unparsable => "value balance is unparsable".to_string(),
431        })
432    }
433}
434
435impl<C> std::ops::Add for ValueBalance<C>
436where
437    C: Constraint,
438{
439    type Output = Result<ValueBalance<C>, ValueBalanceError>;
440    fn add(self, rhs: ValueBalance<C>) -> Self::Output {
441        Ok(ValueBalance::<C> {
442            transparent: (self.transparent + rhs.transparent).map_err(Transparent)?,
443            sprout: (self.sprout + rhs.sprout).map_err(Sprout)?,
444            sapling: (self.sapling + rhs.sapling).map_err(Sapling)?,
445            orchard: (self.orchard + rhs.orchard).map_err(Orchard)?,
446            deferred: (self.deferred + rhs.deferred).map_err(Deferred)?,
447        })
448    }
449}
450
451impl<C> std::ops::Add<ValueBalance<C>> for Result<ValueBalance<C>, ValueBalanceError>
452where
453    C: Constraint,
454{
455    type Output = Result<ValueBalance<C>, ValueBalanceError>;
456    fn add(self, rhs: ValueBalance<C>) -> Self::Output {
457        self? + rhs
458    }
459}
460
461impl<C> std::ops::Add<Result<ValueBalance<C>, ValueBalanceError>> for ValueBalance<C>
462where
463    C: Constraint,
464{
465    type Output = Result<ValueBalance<C>, ValueBalanceError>;
466
467    fn add(self, rhs: Result<ValueBalance<C>, ValueBalanceError>) -> Self::Output {
468        self + rhs?
469    }
470}
471
472impl<C> std::ops::AddAssign<ValueBalance<C>> for Result<ValueBalance<C>, ValueBalanceError>
473where
474    ValueBalance<C>: Copy,
475    C: Constraint,
476{
477    fn add_assign(&mut self, rhs: ValueBalance<C>) {
478        if let Ok(lhs) = *self {
479            *self = lhs + rhs;
480        }
481    }
482}
483
484impl<C> std::ops::Sub for ValueBalance<C>
485where
486    C: Constraint,
487{
488    type Output = Result<ValueBalance<C>, ValueBalanceError>;
489    fn sub(self, rhs: ValueBalance<C>) -> Self::Output {
490        Ok(ValueBalance::<C> {
491            transparent: (self.transparent - rhs.transparent).map_err(Transparent)?,
492            sprout: (self.sprout - rhs.sprout).map_err(Sprout)?,
493            sapling: (self.sapling - rhs.sapling).map_err(Sapling)?,
494            orchard: (self.orchard - rhs.orchard).map_err(Orchard)?,
495            deferred: (self.deferred - rhs.deferred).map_err(Deferred)?,
496        })
497    }
498}
499impl<C> std::ops::Sub<ValueBalance<C>> for Result<ValueBalance<C>, ValueBalanceError>
500where
501    C: Constraint,
502{
503    type Output = Result<ValueBalance<C>, ValueBalanceError>;
504    fn sub(self, rhs: ValueBalance<C>) -> Self::Output {
505        self? - rhs
506    }
507}
508
509impl<C> std::ops::Sub<Result<ValueBalance<C>, ValueBalanceError>> for ValueBalance<C>
510where
511    C: Constraint,
512{
513    type Output = Result<ValueBalance<C>, ValueBalanceError>;
514
515    fn sub(self, rhs: Result<ValueBalance<C>, ValueBalanceError>) -> Self::Output {
516        self - rhs?
517    }
518}
519
520impl<C> std::ops::SubAssign<ValueBalance<C>> for Result<ValueBalance<C>, ValueBalanceError>
521where
522    ValueBalance<C>: Copy,
523    C: Constraint,
524{
525    fn sub_assign(&mut self, rhs: ValueBalance<C>) {
526        if let Ok(lhs) = *self {
527            *self = lhs - rhs;
528        }
529    }
530}
531
532impl<C> std::iter::Sum<ValueBalance<C>> for Result<ValueBalance<C>, ValueBalanceError>
533where
534    C: Constraint + Copy,
535{
536    fn sum<I: Iterator<Item = ValueBalance<C>>>(mut iter: I) -> Self {
537        iter.try_fold(ValueBalance::zero(), |acc, value_balance| {
538            acc + value_balance
539        })
540    }
541}
542
543impl<'amt, C> std::iter::Sum<&'amt ValueBalance<C>> for Result<ValueBalance<C>, ValueBalanceError>
544where
545    C: Constraint + std::marker::Copy + 'amt,
546{
547    fn sum<I: Iterator<Item = &'amt ValueBalance<C>>>(iter: I) -> Self {
548        iter.copied().sum()
549    }
550}
551
552impl<C> std::ops::Neg for ValueBalance<C>
553where
554    C: Constraint,
555{
556    type Output = ValueBalance<NegativeAllowed>;
557
558    fn neg(self) -> Self::Output {
559        ValueBalance::<NegativeAllowed> {
560            transparent: self.transparent.neg(),
561            sprout: self.sprout.neg(),
562            sapling: self.sapling.neg(),
563            orchard: self.orchard.neg(),
564            deferred: self.deferred.neg(),
565        }
566    }
567}