Skip to main content

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