2020-08-15 21:55:28 -07:00
|
|
|
//! Transactions and transaction-related structures.
|
2019-09-19 07:45:37 -07:00
|
|
|
|
2022-03-18 13:30:16 -07:00
|
|
|
use std::{collections::HashMap, fmt, iter};
|
|
|
|
|
2021-07-29 06:37:18 -07:00
|
|
|
use halo2::pasta::pallas;
|
2020-06-15 15:08:14 -07:00
|
|
|
|
2021-08-13 09:58:04 -07:00
|
|
|
mod auth_digest;
|
2019-12-05 12:56:58 -08:00
|
|
|
mod hash;
|
|
|
|
mod joinsplit;
|
2020-08-14 23:37:25 -07:00
|
|
|
mod lock_time;
|
2020-08-15 18:50:18 -07:00
|
|
|
mod memo;
|
2019-12-05 12:56:58 -08:00
|
|
|
mod serialize;
|
2020-09-05 16:31:11 -07:00
|
|
|
mod sighash;
|
2021-07-06 05:58:22 -07:00
|
|
|
mod txid;
|
2021-08-17 22:52:42 -07:00
|
|
|
mod unmined;
|
2019-12-04 21:47:50 -08:00
|
|
|
|
2022-11-09 16:12:27 -08:00
|
|
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
|
|
|
pub mod builder;
|
|
|
|
|
2020-09-23 18:52:52 -07:00
|
|
|
#[cfg(any(test, feature = "proptest-impl"))]
|
2022-06-27 23:22:07 -07:00
|
|
|
#[allow(clippy::unwrap_in_result)]
|
2021-04-27 17:43:00 -07:00
|
|
|
pub mod arbitrary;
|
2019-12-05 12:56:58 -08:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests;
|
2019-09-19 07:45:37 -07:00
|
|
|
|
2021-08-13 09:58:04 -07:00
|
|
|
pub use auth_digest::AuthDigest;
|
2021-08-16 14:26:08 -07:00
|
|
|
pub use hash::{Hash, WtxId};
|
2020-08-17 02:06:38 -07:00
|
|
|
pub use joinsplit::JoinSplitData;
|
2020-08-14 23:37:25 -07:00
|
|
|
pub use lock_time::LockTime;
|
2020-08-15 18:50:18 -07:00
|
|
|
pub use memo::Memo;
|
2021-04-07 05:34:58 -07:00
|
|
|
pub use sapling::FieldNotPresent;
|
2023-04-13 01:42:17 -07:00
|
|
|
pub use serialize::{
|
|
|
|
SerializedTransaction, MIN_TRANSPARENT_TX_SIZE, MIN_TRANSPARENT_TX_V4_SIZE,
|
|
|
|
MIN_TRANSPARENT_TX_V5_SIZE,
|
|
|
|
};
|
2021-08-17 22:52:42 -07:00
|
|
|
pub use sighash::{HashType, SigHash};
|
2023-05-01 17:13:33 -07:00
|
|
|
pub use unmined::{
|
|
|
|
zip317, UnminedTx, UnminedTxId, VerifiedUnminedTx, MEMPOOL_TRANSACTION_COST_THRESHOLD,
|
|
|
|
};
|
2019-09-19 07:45:37 -07:00
|
|
|
|
2020-08-17 01:24:33 -07:00
|
|
|
use crate::{
|
2021-07-28 20:49:36 -07:00
|
|
|
amount::{Amount, Error as AmountError, NegativeAllowed, NonNegative},
|
|
|
|
block, orchard,
|
2020-09-05 16:31:11 -07:00
|
|
|
parameters::NetworkUpgrade,
|
2021-12-10 08:33:15 -08:00
|
|
|
primitives::{ed25519, Bctv14Proof, Groth16Proof},
|
2021-07-28 21:23:50 -07:00
|
|
|
sapling, sprout,
|
|
|
|
transparent::{
|
2021-08-09 10:22:26 -07:00
|
|
|
self, outputs_from_utxos,
|
2021-07-28 21:23:50 -07:00
|
|
|
CoinbaseSpendRestriction::{self, *},
|
|
|
|
},
|
2021-08-09 10:22:26 -07:00
|
|
|
value_balance::{ValueBalance, ValueBalanceError},
|
2020-08-17 01:24:33 -07:00
|
|
|
};
|
2019-09-19 07:45:37 -07:00
|
|
|
|
2019-12-05 12:56:58 -08:00
|
|
|
/// A Zcash transaction.
|
2019-09-25 18:25:46 -07:00
|
|
|
///
|
2019-12-05 12:56:58 -08:00
|
|
|
/// A transaction is an encoded data structure that facilitates the transfer of
|
|
|
|
/// value between two public key addresses on the Zcash ecosystem. Everything is
|
2021-12-09 08:50:26 -08:00
|
|
|
/// designed to ensure that transactions can be created, propagated on the
|
|
|
|
/// network, validated, and finally added to the global ledger of transactions
|
|
|
|
/// (the blockchain).
|
2019-09-25 18:25:46 -07:00
|
|
|
///
|
2019-12-05 12:56:58 -08:00
|
|
|
/// Zcash has a number of different transaction formats. They are represented
|
2021-03-12 10:09:39 -08:00
|
|
|
/// internally by different enum variants. Because we checkpoint on Canopy
|
2021-02-25 17:35:54 -08:00
|
|
|
/// activation, we do not validate any pre-Sapling transaction types.
|
2022-03-18 13:30:16 -07:00
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
|
|
#[cfg_attr(any(test, feature = "proptest-impl"), derive(Serialize))]
|
2019-12-05 12:56:58 -08:00
|
|
|
pub enum Transaction {
|
|
|
|
/// A fully transparent transaction (`version = 1`).
|
|
|
|
V1 {
|
|
|
|
/// The transparent inputs to the transaction.
|
2020-08-17 01:24:33 -07:00
|
|
|
inputs: Vec<transparent::Input>,
|
2019-12-05 12:56:58 -08:00
|
|
|
/// The transparent outputs from the transaction.
|
2020-08-17 01:24:33 -07:00
|
|
|
outputs: Vec<transparent::Output>,
|
2019-12-05 12:56:58 -08:00
|
|
|
/// The earliest time or block height that this transaction can be added to the
|
|
|
|
/// chain.
|
|
|
|
lock_time: LockTime,
|
|
|
|
},
|
|
|
|
/// A Sprout transaction (`version = 2`).
|
|
|
|
V2 {
|
|
|
|
/// The transparent inputs to the transaction.
|
2020-08-17 01:24:33 -07:00
|
|
|
inputs: Vec<transparent::Input>,
|
2019-12-05 12:56:58 -08:00
|
|
|
/// The transparent outputs from the transaction.
|
2020-08-17 01:24:33 -07:00
|
|
|
outputs: Vec<transparent::Output>,
|
2019-12-05 12:56:58 -08:00
|
|
|
/// The earliest time or block height that this transaction can be added to the
|
|
|
|
/// chain.
|
|
|
|
lock_time: LockTime,
|
|
|
|
/// The JoinSplit data for this transaction, if any.
|
|
|
|
joinsplit_data: Option<JoinSplitData<Bctv14Proof>>,
|
|
|
|
},
|
|
|
|
/// An Overwinter transaction (`version = 3`).
|
|
|
|
V3 {
|
|
|
|
/// The transparent inputs to the transaction.
|
2020-08-17 01:24:33 -07:00
|
|
|
inputs: Vec<transparent::Input>,
|
2019-12-05 12:56:58 -08:00
|
|
|
/// The transparent outputs from the transaction.
|
2020-08-17 01:24:33 -07:00
|
|
|
outputs: Vec<transparent::Output>,
|
2019-12-05 12:56:58 -08:00
|
|
|
/// The earliest time or block height that this transaction can be added to the
|
|
|
|
/// chain.
|
|
|
|
lock_time: LockTime,
|
|
|
|
/// The latest block height that this transaction can be added to the chain.
|
2020-08-16 11:42:02 -07:00
|
|
|
expiry_height: block::Height,
|
2019-12-05 12:56:58 -08:00
|
|
|
/// The JoinSplit data for this transaction, if any.
|
|
|
|
joinsplit_data: Option<JoinSplitData<Bctv14Proof>>,
|
|
|
|
},
|
|
|
|
/// A Sapling transaction (`version = 4`).
|
|
|
|
V4 {
|
|
|
|
/// The transparent inputs to the transaction.
|
2020-08-17 01:24:33 -07:00
|
|
|
inputs: Vec<transparent::Input>,
|
2019-12-05 12:56:58 -08:00
|
|
|
/// The transparent outputs from the transaction.
|
2020-08-17 01:24:33 -07:00
|
|
|
outputs: Vec<transparent::Output>,
|
2019-12-05 12:56:58 -08:00
|
|
|
/// The earliest time or block height that this transaction can be added to the
|
|
|
|
/// chain.
|
|
|
|
lock_time: LockTime,
|
|
|
|
/// The latest block height that this transaction can be added to the chain.
|
2020-08-16 11:42:02 -07:00
|
|
|
expiry_height: block::Height,
|
2019-12-05 12:56:58 -08:00
|
|
|
/// The JoinSplit data for this transaction, if any.
|
|
|
|
joinsplit_data: Option<JoinSplitData<Groth16Proof>>,
|
2021-03-31 14:34:25 -07:00
|
|
|
/// The sapling shielded data for this transaction, if any.
|
|
|
|
sapling_shielded_data: Option<sapling::ShieldedData<sapling::PerSpendAnchor>>,
|
2019-12-05 12:56:58 -08:00
|
|
|
},
|
2021-11-30 02:05:58 -08:00
|
|
|
/// A `version = 5` transaction , which supports Orchard, Sapling, and transparent, but not Sprout.
|
2021-03-03 13:56:41 -08:00
|
|
|
V5 {
|
2021-04-28 18:55:29 -07:00
|
|
|
/// The Network Upgrade for this transaction.
|
|
|
|
///
|
|
|
|
/// Derived from the ConsensusBranchId field.
|
|
|
|
network_upgrade: NetworkUpgrade,
|
2021-03-03 13:56:41 -08:00
|
|
|
/// The earliest time or block height that this transaction can be added to the
|
|
|
|
/// chain.
|
|
|
|
lock_time: LockTime,
|
|
|
|
/// The latest block height that this transaction can be added to the chain.
|
|
|
|
expiry_height: block::Height,
|
|
|
|
/// The transparent inputs to the transaction.
|
|
|
|
inputs: Vec<transparent::Input>,
|
|
|
|
/// The transparent outputs from the transaction.
|
|
|
|
outputs: Vec<transparent::Output>,
|
2021-04-15 15:19:28 -07:00
|
|
|
/// The sapling shielded data for this transaction, if any.
|
|
|
|
sapling_shielded_data: Option<sapling::ShieldedData<sapling::SharedAnchor>>,
|
2021-06-01 18:32:52 -07:00
|
|
|
/// The orchard data for this transaction, if any.
|
2021-05-20 17:42:06 -07:00
|
|
|
orchard_shielded_data: Option<orchard::ShieldedData>,
|
2021-03-03 13:56:41 -08:00
|
|
|
},
|
2019-12-04 21:47:50 -08:00
|
|
|
}
|
2020-02-09 20:55:52 -08:00
|
|
|
|
Refactor mempool spend conflict checks to increase performance (#2826)
* Add `HashSet`s to help spend conflict detection
Keep track of the spent transparent outpoints and the revealed
nullifiers.
Clippy complained that the `ActiveState` had variants with large size
differences, but that was expected, so I disabled that lint on that
`enum`.
* Clear the `HashSet`s when clearing the mempool
Clear them so that they remain consistent with the set of verified
transactions.
* Use `HashSet`s to check for spend conflicts
Store new outputs into its respective `HashSet`, and abort if a
duplicate output is found.
* Remove inserted outputs when aborting
Restore the `HashSet` to its previous state.
* Remove tracked outputs when removing a transaction
Keep the mempool storage in a consistent state when a transaction is
removed.
* Remove tracked outputs when evicting from mempool
Ensure eviction also keeps the tracked outputs consistent with the
verified transactions.
* Refactor to create a `VerifiedSet` helper type
Move the code to handle the output caches into the new type. Also move
the eviction code to make things a little simpler.
* Refactor to have a single `remove` method
Centralize the code that handles the removal of a transaction to avoid
mistakes.
* Move mempool size limiting back to `Storage`
Because the evicted transactions must be added to the rejected list.
* Remove leftover `dbg!` statement
Leftover from some temporary testing code.
Co-authored-by: teor <teor@riseup.net>
* Remove unnecessary `TODO`
It is more speculation than planning, so it doesn't add much value.
Co-authored-by: teor <teor@riseup.net>
* Fix typo in documentation
The verb should match the subject "transactions" which is plural.
Co-authored-by: teor <teor@riseup.net>
* Add a comment to warn about correctness
There's a subtle but important detail in the implementation that should
be made more visible to avoid mistakes in the future.
Co-authored-by: teor <teor@riseup.net>
* Remove outdated comment
Left-over from the attempt to move the eviction into the `VerifiedSet`.
* Improve comment explaining lint removal
Rewrite the comment explaining why the Clippy lint was ignored.
* Check for spend conflicts in `VerifiedSet`
Refactor to avoid API misuse.
* Test rejected transaction rollback
Using two transactions, perform the same test adding a conflict to both
of them to check if the second inserted transaction is properly
rejected. Then remove any conflicts from the second transaction and add
it again. That should work, because if it doesn't it means that when the
second transaction was rejected it left things it shouldn't in the
cache.
* Test removal of multiple transactions
When removing multiple transactions from the mempool storage, all of the
ones requested should be removed and any other transaction should be
still be there afterwards.
* Increase mempool size to 4, so that spend conflict tests work
If the mempool size is smaller than 4,
these tests don't fail on a trivial removal bug.
Because we need a minimum number of transactions in the mempool
to trigger the bug.
Also commit a proptest seed that fails on a trivial removal bug.
(This seed fails if we remove indexes in order,
because every index past the first removes the wrong transaction.)
* Summarise transaction data in proptest error output
* Summarise spend conflict field data in proptest error output
* Summarise multiple removal field data in proptest error output
And replace the very large proptest debug output with the new summary.
Co-authored-by: teor <teor@riseup.net>
2021-10-10 16:54:46 -07:00
|
|
|
impl fmt::Display for Transaction {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
let mut fmter = f.debug_struct("Transaction");
|
|
|
|
|
|
|
|
fmter.field("version", &self.version());
|
2022-12-12 15:19:45 -08:00
|
|
|
|
Refactor mempool spend conflict checks to increase performance (#2826)
* Add `HashSet`s to help spend conflict detection
Keep track of the spent transparent outpoints and the revealed
nullifiers.
Clippy complained that the `ActiveState` had variants with large size
differences, but that was expected, so I disabled that lint on that
`enum`.
* Clear the `HashSet`s when clearing the mempool
Clear them so that they remain consistent with the set of verified
transactions.
* Use `HashSet`s to check for spend conflicts
Store new outputs into its respective `HashSet`, and abort if a
duplicate output is found.
* Remove inserted outputs when aborting
Restore the `HashSet` to its previous state.
* Remove tracked outputs when removing a transaction
Keep the mempool storage in a consistent state when a transaction is
removed.
* Remove tracked outputs when evicting from mempool
Ensure eviction also keeps the tracked outputs consistent with the
verified transactions.
* Refactor to create a `VerifiedSet` helper type
Move the code to handle the output caches into the new type. Also move
the eviction code to make things a little simpler.
* Refactor to have a single `remove` method
Centralize the code that handles the removal of a transaction to avoid
mistakes.
* Move mempool size limiting back to `Storage`
Because the evicted transactions must be added to the rejected list.
* Remove leftover `dbg!` statement
Leftover from some temporary testing code.
Co-authored-by: teor <teor@riseup.net>
* Remove unnecessary `TODO`
It is more speculation than planning, so it doesn't add much value.
Co-authored-by: teor <teor@riseup.net>
* Fix typo in documentation
The verb should match the subject "transactions" which is plural.
Co-authored-by: teor <teor@riseup.net>
* Add a comment to warn about correctness
There's a subtle but important detail in the implementation that should
be made more visible to avoid mistakes in the future.
Co-authored-by: teor <teor@riseup.net>
* Remove outdated comment
Left-over from the attempt to move the eviction into the `VerifiedSet`.
* Improve comment explaining lint removal
Rewrite the comment explaining why the Clippy lint was ignored.
* Check for spend conflicts in `VerifiedSet`
Refactor to avoid API misuse.
* Test rejected transaction rollback
Using two transactions, perform the same test adding a conflict to both
of them to check if the second inserted transaction is properly
rejected. Then remove any conflicts from the second transaction and add
it again. That should work, because if it doesn't it means that when the
second transaction was rejected it left things it shouldn't in the
cache.
* Test removal of multiple transactions
When removing multiple transactions from the mempool storage, all of the
ones requested should be removed and any other transaction should be
still be there afterwards.
* Increase mempool size to 4, so that spend conflict tests work
If the mempool size is smaller than 4,
these tests don't fail on a trivial removal bug.
Because we need a minimum number of transactions in the mempool
to trigger the bug.
Also commit a proptest seed that fails on a trivial removal bug.
(This seed fails if we remove indexes in order,
because every index past the first removes the wrong transaction.)
* Summarise transaction data in proptest error output
* Summarise spend conflict field data in proptest error output
* Summarise multiple removal field data in proptest error output
And replace the very large proptest debug output with the new summary.
Co-authored-by: teor <teor@riseup.net>
2021-10-10 16:54:46 -07:00
|
|
|
if let Some(network_upgrade) = self.network_upgrade() {
|
|
|
|
fmter.field("network_upgrade", &network_upgrade);
|
|
|
|
}
|
|
|
|
|
2022-12-12 15:19:45 -08:00
|
|
|
if let Some(lock_time) = self.lock_time() {
|
|
|
|
fmter.field("lock_time", &lock_time);
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(expiry_height) = self.expiry_height() {
|
|
|
|
fmter.field("expiry_height", &expiry_height);
|
|
|
|
}
|
|
|
|
|
Refactor mempool spend conflict checks to increase performance (#2826)
* Add `HashSet`s to help spend conflict detection
Keep track of the spent transparent outpoints and the revealed
nullifiers.
Clippy complained that the `ActiveState` had variants with large size
differences, but that was expected, so I disabled that lint on that
`enum`.
* Clear the `HashSet`s when clearing the mempool
Clear them so that they remain consistent with the set of verified
transactions.
* Use `HashSet`s to check for spend conflicts
Store new outputs into its respective `HashSet`, and abort if a
duplicate output is found.
* Remove inserted outputs when aborting
Restore the `HashSet` to its previous state.
* Remove tracked outputs when removing a transaction
Keep the mempool storage in a consistent state when a transaction is
removed.
* Remove tracked outputs when evicting from mempool
Ensure eviction also keeps the tracked outputs consistent with the
verified transactions.
* Refactor to create a `VerifiedSet` helper type
Move the code to handle the output caches into the new type. Also move
the eviction code to make things a little simpler.
* Refactor to have a single `remove` method
Centralize the code that handles the removal of a transaction to avoid
mistakes.
* Move mempool size limiting back to `Storage`
Because the evicted transactions must be added to the rejected list.
* Remove leftover `dbg!` statement
Leftover from some temporary testing code.
Co-authored-by: teor <teor@riseup.net>
* Remove unnecessary `TODO`
It is more speculation than planning, so it doesn't add much value.
Co-authored-by: teor <teor@riseup.net>
* Fix typo in documentation
The verb should match the subject "transactions" which is plural.
Co-authored-by: teor <teor@riseup.net>
* Add a comment to warn about correctness
There's a subtle but important detail in the implementation that should
be made more visible to avoid mistakes in the future.
Co-authored-by: teor <teor@riseup.net>
* Remove outdated comment
Left-over from the attempt to move the eviction into the `VerifiedSet`.
* Improve comment explaining lint removal
Rewrite the comment explaining why the Clippy lint was ignored.
* Check for spend conflicts in `VerifiedSet`
Refactor to avoid API misuse.
* Test rejected transaction rollback
Using two transactions, perform the same test adding a conflict to both
of them to check if the second inserted transaction is properly
rejected. Then remove any conflicts from the second transaction and add
it again. That should work, because if it doesn't it means that when the
second transaction was rejected it left things it shouldn't in the
cache.
* Test removal of multiple transactions
When removing multiple transactions from the mempool storage, all of the
ones requested should be removed and any other transaction should be
still be there afterwards.
* Increase mempool size to 4, so that spend conflict tests work
If the mempool size is smaller than 4,
these tests don't fail on a trivial removal bug.
Because we need a minimum number of transactions in the mempool
to trigger the bug.
Also commit a proptest seed that fails on a trivial removal bug.
(This seed fails if we remove indexes in order,
because every index past the first removes the wrong transaction.)
* Summarise transaction data in proptest error output
* Summarise spend conflict field data in proptest error output
* Summarise multiple removal field data in proptest error output
And replace the very large proptest debug output with the new summary.
Co-authored-by: teor <teor@riseup.net>
2021-10-10 16:54:46 -07:00
|
|
|
fmter.field("transparent_inputs", &self.inputs().len());
|
|
|
|
fmter.field("transparent_outputs", &self.outputs().len());
|
|
|
|
fmter.field("sprout_joinsplits", &self.joinsplit_count());
|
|
|
|
fmter.field("sapling_spends", &self.sapling_spends_per_anchor().count());
|
|
|
|
fmter.field("sapling_outputs", &self.sapling_outputs().count());
|
|
|
|
fmter.field("orchard_actions", &self.orchard_actions().count());
|
|
|
|
|
|
|
|
fmter.field("unmined_id", &self.unmined_id());
|
|
|
|
|
|
|
|
fmter.finish()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-09 20:55:52 -08:00
|
|
|
impl Transaction {
|
2021-08-25 14:02:47 -07:00
|
|
|
// identifiers and hashes
|
2021-04-27 17:43:00 -07:00
|
|
|
|
2021-08-25 14:02:47 -07:00
|
|
|
/// Compute the hash (mined transaction ID) of this transaction.
|
|
|
|
///
|
|
|
|
/// The hash uniquely identifies mined v5 transactions,
|
|
|
|
/// and all v1-v4 transactions, whether mined or unmined.
|
2020-09-02 14:59:16 -07:00
|
|
|
pub fn hash(&self) -> Hash {
|
|
|
|
Hash::from(self)
|
|
|
|
}
|
|
|
|
|
2021-08-25 14:02:47 -07:00
|
|
|
/// Compute the unmined transaction ID of this transaction.
|
|
|
|
///
|
|
|
|
/// This ID uniquely identifies unmined transactions,
|
|
|
|
/// regardless of version.
|
|
|
|
pub fn unmined_id(&self) -> UnminedTxId {
|
|
|
|
UnminedTxId::from(self)
|
|
|
|
}
|
|
|
|
|
2021-04-27 17:43:00 -07:00
|
|
|
/// Calculate the sighash for the current transaction
|
|
|
|
///
|
|
|
|
/// # Details
|
|
|
|
///
|
|
|
|
/// The `input` argument indicates the transparent Input for which we are
|
|
|
|
/// producing a sighash. It is comprised of the index identifying the
|
|
|
|
/// transparent::Input within the transaction and the transparent::Output
|
|
|
|
/// representing the UTXO being spent by that input.
|
|
|
|
///
|
|
|
|
/// # Panics
|
|
|
|
///
|
|
|
|
/// - if passed in any NetworkUpgrade from before NetworkUpgrade::Overwinter
|
|
|
|
/// - if called on a v1 or v2 transaction
|
|
|
|
/// - if the input index points to a transparent::Input::CoinBase
|
|
|
|
/// - if the input index is out of bounds for self.inputs()
|
|
|
|
pub fn sighash(
|
|
|
|
&self,
|
|
|
|
network_upgrade: NetworkUpgrade,
|
|
|
|
hash_type: sighash::HashType,
|
2022-01-31 07:28:42 -08:00
|
|
|
all_previous_outputs: &[transparent::Output],
|
|
|
|
input: Option<usize>,
|
2021-07-06 15:27:10 -07:00
|
|
|
) -> SigHash {
|
2022-01-31 07:28:42 -08:00
|
|
|
sighash::SigHasher::new(
|
|
|
|
self,
|
|
|
|
hash_type,
|
|
|
|
network_upgrade,
|
|
|
|
all_previous_outputs,
|
|
|
|
input,
|
|
|
|
)
|
|
|
|
.sighash()
|
2021-04-27 17:43:00 -07:00
|
|
|
}
|
|
|
|
|
2021-08-13 09:58:04 -07:00
|
|
|
/// Compute the authorizing data commitment of this transaction as specified
|
|
|
|
/// in [ZIP-244].
|
|
|
|
///
|
|
|
|
/// Returns None for pre-v5 transactions.
|
|
|
|
///
|
|
|
|
/// [ZIP-244]: https://zips.z.cash/zip-0244.
|
|
|
|
pub fn auth_digest(&self) -> Option<AuthDigest> {
|
|
|
|
match self {
|
|
|
|
Transaction::V1 { .. }
|
|
|
|
| Transaction::V2 { .. }
|
|
|
|
| Transaction::V3 { .. }
|
|
|
|
| Transaction::V4 { .. } => None,
|
|
|
|
Transaction::V5 { .. } => Some(AuthDigest::from(self)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-28 21:23:50 -07:00
|
|
|
// other properties
|
|
|
|
|
|
|
|
/// Does this transaction have transparent or shielded inputs?
|
|
|
|
pub fn has_transparent_or_shielded_inputs(&self) -> bool {
|
|
|
|
!self.inputs().is_empty() || self.has_shielded_inputs()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Does this transaction have shielded inputs?
|
|
|
|
///
|
2022-06-02 08:07:35 -07:00
|
|
|
/// See [`Self::has_transparent_or_shielded_inputs`] for details.
|
2021-07-28 21:23:50 -07:00
|
|
|
pub fn has_shielded_inputs(&self) -> bool {
|
|
|
|
self.joinsplit_count() > 0
|
|
|
|
|| self.sapling_spends_per_anchor().count() > 0
|
|
|
|
|| (self.orchard_actions().count() > 0
|
|
|
|
&& self
|
|
|
|
.orchard_flags()
|
|
|
|
.unwrap_or_else(orchard::Flags::empty)
|
|
|
|
.contains(orchard::Flags::ENABLE_SPENDS))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Does this transaction have transparent or shielded outputs?
|
|
|
|
pub fn has_transparent_or_shielded_outputs(&self) -> bool {
|
|
|
|
!self.outputs().is_empty() || self.has_shielded_outputs()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Does this transaction have shielded outputs?
|
|
|
|
///
|
2022-06-02 08:07:35 -07:00
|
|
|
/// See [`Self::has_transparent_or_shielded_outputs`] for details.
|
2021-07-28 21:23:50 -07:00
|
|
|
pub fn has_shielded_outputs(&self) -> bool {
|
|
|
|
self.joinsplit_count() > 0
|
|
|
|
|| self.sapling_outputs().count() > 0
|
|
|
|
|| (self.orchard_actions().count() > 0
|
|
|
|
&& self
|
|
|
|
.orchard_flags()
|
|
|
|
.unwrap_or_else(orchard::Flags::empty)
|
|
|
|
.contains(orchard::Flags::ENABLE_OUTPUTS))
|
|
|
|
}
|
|
|
|
|
2021-11-08 13:45:54 -08:00
|
|
|
/// Does this transaction has at least one flag when we have at least one orchard action?
|
|
|
|
pub fn has_enough_orchard_flags(&self) -> bool {
|
|
|
|
if self.version() < 5 || self.orchard_actions().count() == 0 {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
self.orchard_flags()
|
|
|
|
.unwrap_or_else(orchard::Flags::empty)
|
|
|
|
.intersects(orchard::Flags::ENABLE_SPENDS | orchard::Flags::ENABLE_OUTPUTS)
|
|
|
|
}
|
|
|
|
|
2021-07-28 21:23:50 -07:00
|
|
|
/// Returns the [`CoinbaseSpendRestriction`] for this transaction,
|
|
|
|
/// assuming it is mined at `spend_height`.
|
|
|
|
pub fn coinbase_spend_restriction(
|
|
|
|
&self,
|
|
|
|
spend_height: block::Height,
|
|
|
|
) -> CoinbaseSpendRestriction {
|
|
|
|
if self.outputs().is_empty() {
|
|
|
|
// we know this transaction must have shielded outputs,
|
|
|
|
// because of other consensus rules
|
|
|
|
OnlyShieldedOutputs { spend_height }
|
|
|
|
} else {
|
|
|
|
SomeTransparentOutputs
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-27 17:43:00 -07:00
|
|
|
// header
|
|
|
|
|
2021-06-14 17:15:59 -07:00
|
|
|
/// Return if the `fOverwintered` flag of this transaction is set.
|
|
|
|
pub fn is_overwintered(&self) -> bool {
|
|
|
|
match self {
|
|
|
|
Transaction::V1 { .. } | Transaction::V2 { .. } => false,
|
|
|
|
Transaction::V3 { .. } | Transaction::V4 { .. } | Transaction::V5 { .. } => true,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Return the version of this transaction.
|
|
|
|
pub fn version(&self) -> u32 {
|
|
|
|
match self {
|
|
|
|
Transaction::V1 { .. } => 1,
|
|
|
|
Transaction::V2 { .. } => 2,
|
|
|
|
Transaction::V3 { .. } => 3,
|
|
|
|
Transaction::V4 { .. } => 4,
|
|
|
|
Transaction::V5 { .. } => 5,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-27 17:43:00 -07:00
|
|
|
/// Get this transaction's lock time.
|
2021-11-22 21:53:53 -08:00
|
|
|
pub fn lock_time(&self) -> Option<LockTime> {
|
|
|
|
let lock_time = match self {
|
|
|
|
Transaction::V1 { lock_time, .. }
|
|
|
|
| Transaction::V2 { lock_time, .. }
|
|
|
|
| Transaction::V3 { lock_time, .. }
|
|
|
|
| Transaction::V4 { lock_time, .. }
|
|
|
|
| Transaction::V5 { lock_time, .. } => *lock_time,
|
|
|
|
};
|
|
|
|
|
|
|
|
// `zcashd` checks that the block height is greater than the lock height.
|
|
|
|
// This check allows the genesis block transaction, which would otherwise be invalid.
|
|
|
|
// (Or have to use a lock time.)
|
|
|
|
//
|
|
|
|
// It matches the `zcashd` check here:
|
|
|
|
// https://github.com/zcash/zcash/blob/1a7c2a3b04bcad6549be6d571bfdff8af9a2c814/src/main.cpp#L720
|
|
|
|
if lock_time == LockTime::unlocked() {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Consensus rule:
|
|
|
|
//
|
|
|
|
// > The transaction must be finalized: either its locktime must be in the past (or less
|
|
|
|
// > than or equal to the current block height), or all of its sequence numbers must be
|
|
|
|
// > 0xffffffff.
|
|
|
|
//
|
|
|
|
// In `zcashd`, this rule applies to both coinbase and prevout input sequence numbers.
|
|
|
|
//
|
|
|
|
// Unlike Bitcoin, Zcash allows transactions with no transparent inputs. These transactions
|
|
|
|
// only have shielded inputs. Surprisingly, the `zcashd` implementation ignores the lock
|
|
|
|
// time in these transactions. `zcashd` only checks the lock time when it finds a
|
|
|
|
// transparent input sequence number that is not `u32::MAX`.
|
|
|
|
//
|
|
|
|
// https://developer.bitcoin.org/devguide/transactions.html#non-standard-transactions
|
|
|
|
let has_sequence_number_enabling_lock_time = self
|
|
|
|
.inputs()
|
|
|
|
.iter()
|
|
|
|
.map(transparent::Input::sequence)
|
|
|
|
.any(|sequence_number| sequence_number != u32::MAX);
|
|
|
|
|
|
|
|
if has_sequence_number_enabling_lock_time {
|
|
|
|
Some(lock_time)
|
|
|
|
} else {
|
|
|
|
None
|
2021-04-27 17:43:00 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-31 12:42:11 -08:00
|
|
|
/// Returns `true` if this transaction's `lock_time` is a [`LockTime::Time`].
|
|
|
|
/// Returns `false` if it is a [`LockTime::Height`] (locked or unlocked), is unlocked,
|
|
|
|
/// or if the transparent input sequence numbers have disabled lock times.
|
|
|
|
pub fn lock_time_is_time(&self) -> bool {
|
|
|
|
if let Some(lock_time) = self.lock_time() {
|
|
|
|
return lock_time.is_time();
|
|
|
|
}
|
|
|
|
|
|
|
|
false
|
|
|
|
}
|
|
|
|
|
2021-04-27 17:43:00 -07:00
|
|
|
/// Get this transaction's expiry height, if any.
|
|
|
|
pub fn expiry_height(&self) -> Option<block::Height> {
|
|
|
|
match self {
|
2021-09-29 09:52:44 -07:00
|
|
|
Transaction::V1 { .. } | Transaction::V2 { .. } => None,
|
2021-09-21 20:44:52 -07:00
|
|
|
Transaction::V3 { expiry_height, .. }
|
|
|
|
| Transaction::V4 { expiry_height, .. }
|
|
|
|
| Transaction::V5 { expiry_height, .. } => match expiry_height {
|
|
|
|
// Consensus rule:
|
|
|
|
// > No limit: To set no limit on transactions (so that they do not expire), nExpiryHeight should be set to 0.
|
|
|
|
// https://zips.z.cash/zip-0203#specification
|
|
|
|
block::Height(0) => None,
|
|
|
|
block::Height(expiry_height) => Some(block::Height(*expiry_height)),
|
|
|
|
},
|
2021-04-27 17:43:00 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-29 09:52:44 -07:00
|
|
|
/// Modify the expiry height of this transaction.
|
|
|
|
///
|
|
|
|
/// # Panics
|
|
|
|
///
|
|
|
|
/// - if called on a v1 or v2 transaction
|
|
|
|
#[cfg(any(test, feature = "proptest-impl"))]
|
|
|
|
pub fn expiry_height_mut(&mut self) -> &mut block::Height {
|
|
|
|
match self {
|
|
|
|
Transaction::V1 { .. } | Transaction::V2 { .. } => {
|
|
|
|
panic!("v1 and v2 transactions are not supported")
|
|
|
|
}
|
|
|
|
Transaction::V3 {
|
|
|
|
ref mut expiry_height,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Transaction::V4 {
|
|
|
|
ref mut expiry_height,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Transaction::V5 {
|
|
|
|
ref mut expiry_height,
|
|
|
|
..
|
|
|
|
} => expiry_height,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-21 19:06:52 -07:00
|
|
|
/// Get this transaction's network upgrade field, if any.
|
|
|
|
/// This field is serialized as `nConsensusBranchId` ([7.1]).
|
|
|
|
///
|
|
|
|
/// [7.1]: https://zips.z.cash/protocol/nu5.pdf#txnencodingandconsensus
|
|
|
|
pub fn network_upgrade(&self) -> Option<NetworkUpgrade> {
|
|
|
|
match self {
|
|
|
|
Transaction::V1 { .. }
|
|
|
|
| Transaction::V2 { .. }
|
|
|
|
| Transaction::V3 { .. }
|
|
|
|
| Transaction::V4 { .. } => None,
|
|
|
|
Transaction::V5 {
|
|
|
|
network_upgrade, ..
|
|
|
|
} => Some(*network_upgrade),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-27 17:43:00 -07:00
|
|
|
// transparent
|
|
|
|
|
2020-08-13 14:04:43 -07:00
|
|
|
/// Access the transparent inputs of this transaction, regardless of version.
|
2020-08-17 01:24:33 -07:00
|
|
|
pub fn inputs(&self) -> &[transparent::Input] {
|
2020-02-09 20:55:52 -08:00
|
|
|
match self {
|
2020-08-13 14:04:43 -07:00
|
|
|
Transaction::V1 { ref inputs, .. } => inputs,
|
|
|
|
Transaction::V2 { ref inputs, .. } => inputs,
|
|
|
|
Transaction::V3 { ref inputs, .. } => inputs,
|
|
|
|
Transaction::V4 { ref inputs, .. } => inputs,
|
2021-03-03 13:56:41 -08:00
|
|
|
Transaction::V5 { ref inputs, .. } => inputs,
|
2020-02-09 20:55:52 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-22 16:40:15 -07:00
|
|
|
/// Modify the transparent inputs of this transaction, regardless of version.
|
|
|
|
#[cfg(any(test, feature = "proptest-impl"))]
|
|
|
|
pub fn inputs_mut(&mut self) -> &mut Vec<transparent::Input> {
|
|
|
|
match self {
|
|
|
|
Transaction::V1 { ref mut inputs, .. } => inputs,
|
|
|
|
Transaction::V2 { ref mut inputs, .. } => inputs,
|
|
|
|
Transaction::V3 { ref mut inputs, .. } => inputs,
|
|
|
|
Transaction::V4 { ref mut inputs, .. } => inputs,
|
|
|
|
Transaction::V5 { ref mut inputs, .. } => inputs,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-27 18:03:08 -07:00
|
|
|
/// Access the [`transparent::OutPoint`]s spent by this transaction's [`transparent::Input`]s.
|
|
|
|
pub fn spent_outpoints(&self) -> impl Iterator<Item = transparent::OutPoint> + '_ {
|
|
|
|
self.inputs()
|
|
|
|
.iter()
|
|
|
|
.filter_map(transparent::Input::outpoint)
|
|
|
|
}
|
|
|
|
|
2020-08-13 14:04:43 -07:00
|
|
|
/// Access the transparent outputs of this transaction, regardless of version.
|
2020-08-17 01:24:33 -07:00
|
|
|
pub fn outputs(&self) -> &[transparent::Output] {
|
2020-02-09 20:55:52 -08:00
|
|
|
match self {
|
2020-08-13 14:04:43 -07:00
|
|
|
Transaction::V1 { ref outputs, .. } => outputs,
|
|
|
|
Transaction::V2 { ref outputs, .. } => outputs,
|
|
|
|
Transaction::V3 { ref outputs, .. } => outputs,
|
|
|
|
Transaction::V4 { ref outputs, .. } => outputs,
|
2021-03-03 13:56:41 -08:00
|
|
|
Transaction::V5 { ref outputs, .. } => outputs,
|
2020-02-09 20:55:52 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-28 21:23:50 -07:00
|
|
|
/// Modify the transparent outputs of this transaction, regardless of version.
|
|
|
|
#[cfg(any(test, feature = "proptest-impl"))]
|
|
|
|
pub fn outputs_mut(&mut self) -> &mut Vec<transparent::Output> {
|
|
|
|
match self {
|
|
|
|
Transaction::V1 {
|
|
|
|
ref mut outputs, ..
|
|
|
|
} => outputs,
|
|
|
|
Transaction::V2 {
|
|
|
|
ref mut outputs, ..
|
|
|
|
} => outputs,
|
|
|
|
Transaction::V3 {
|
|
|
|
ref mut outputs, ..
|
|
|
|
} => outputs,
|
|
|
|
Transaction::V4 {
|
|
|
|
ref mut outputs, ..
|
|
|
|
} => outputs,
|
|
|
|
Transaction::V5 {
|
|
|
|
ref mut outputs, ..
|
|
|
|
} => outputs,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-01 17:06:20 -07:00
|
|
|
/// Returns `true` if this transaction has valid inputs for a coinbase
|
2022-04-20 02:31:12 -07:00
|
|
|
/// transaction, that is, has a single input and it is a coinbase input
|
|
|
|
/// (null prevout).
|
|
|
|
pub fn is_coinbase(&self) -> bool {
|
2021-04-27 17:43:00 -07:00
|
|
|
self.inputs().len() == 1
|
|
|
|
&& matches!(
|
|
|
|
self.inputs().get(0),
|
|
|
|
Some(transparent::Input::Coinbase { .. })
|
|
|
|
)
|
2020-02-09 20:55:52 -08:00
|
|
|
}
|
|
|
|
|
2022-04-20 02:31:12 -07:00
|
|
|
/// Returns `true` if this transaction has valid inputs for a non-coinbase
|
|
|
|
/// transaction, that is, does not have any coinbase input (non-null prevouts).
|
2021-04-27 17:43:00 -07:00
|
|
|
///
|
2022-04-20 02:31:12 -07:00
|
|
|
/// Note that it's possible for a transaction return false in both
|
|
|
|
/// [`Transaction::is_coinbase`] and [`Transaction::is_valid_non_coinbase`],
|
|
|
|
/// though those transactions will be rejected.
|
|
|
|
pub fn is_valid_non_coinbase(&self) -> bool {
|
2021-04-27 17:43:00 -07:00
|
|
|
self.inputs()
|
|
|
|
.iter()
|
2022-04-20 02:31:12 -07:00
|
|
|
.all(|input| matches!(input, transparent::Input::PrevOut { .. }))
|
2021-04-27 17:43:00 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// sprout
|
|
|
|
|
2021-11-30 08:05:35 -08:00
|
|
|
/// Returns the Sprout `JoinSplit<Groth16Proof>`s in this transaction, regardless of version.
|
|
|
|
pub fn sprout_groth16_joinsplits(
|
|
|
|
&self,
|
|
|
|
) -> Box<dyn Iterator<Item = &sprout::JoinSplit<Groth16Proof>> + '_> {
|
|
|
|
match self {
|
|
|
|
// JoinSplits with Groth16 Proofs
|
|
|
|
Transaction::V4 {
|
|
|
|
joinsplit_data: Some(joinsplit_data),
|
|
|
|
..
|
|
|
|
} => Box::new(joinsplit_data.joinsplits()),
|
|
|
|
|
|
|
|
// No JoinSplits / JoinSplits with BCTV14 proofs
|
|
|
|
Transaction::V1 { .. }
|
|
|
|
| Transaction::V2 { .. }
|
|
|
|
| Transaction::V3 { .. }
|
|
|
|
| Transaction::V4 {
|
|
|
|
joinsplit_data: None,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Transaction::V5 { .. } => Box::new(std::iter::empty()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-27 17:43:00 -07:00
|
|
|
/// Returns the number of `JoinSplit`s in this transaction, regardless of version.
|
|
|
|
pub fn joinsplit_count(&self) -> usize {
|
2020-02-09 20:55:52 -08:00
|
|
|
match self {
|
2021-04-27 17:43:00 -07:00
|
|
|
// JoinSplits with Bctv14 Proofs
|
|
|
|
Transaction::V2 {
|
|
|
|
joinsplit_data: Some(joinsplit_data),
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Transaction::V3 {
|
|
|
|
joinsplit_data: Some(joinsplit_data),
|
|
|
|
..
|
|
|
|
} => joinsplit_data.joinsplits().count(),
|
|
|
|
// JoinSplits with Groth Proofs
|
|
|
|
Transaction::V4 {
|
|
|
|
joinsplit_data: Some(joinsplit_data),
|
|
|
|
..
|
|
|
|
} => joinsplit_data.joinsplits().count(),
|
|
|
|
// No JoinSplits
|
|
|
|
Transaction::V1 { .. }
|
|
|
|
| Transaction::V2 {
|
|
|
|
joinsplit_data: None,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Transaction::V3 {
|
|
|
|
joinsplit_data: None,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Transaction::V4 {
|
|
|
|
joinsplit_data: None,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Transaction::V5 { .. } => 0,
|
2020-02-09 20:55:52 -08:00
|
|
|
}
|
|
|
|
}
|
2020-07-09 16:13:44 -07:00
|
|
|
|
2020-10-23 15:29:52 -07:00
|
|
|
/// Access the sprout::Nullifiers in this transaction, regardless of version.
|
|
|
|
pub fn sprout_nullifiers(&self) -> Box<dyn Iterator<Item = &sprout::Nullifier> + '_> {
|
|
|
|
// This function returns a boxed iterator because the different
|
|
|
|
// transaction variants end up having different iterator types
|
2021-04-19 23:22:25 -07:00
|
|
|
// (we could extract bctv and groth as separate iterators, then chain
|
|
|
|
// them together, but that would be much harder to read and maintain)
|
2020-10-23 15:29:52 -07:00
|
|
|
match self {
|
|
|
|
// JoinSplits with Bctv14 Proofs
|
|
|
|
Transaction::V2 {
|
|
|
|
joinsplit_data: Some(joinsplit_data),
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Transaction::V3 {
|
|
|
|
joinsplit_data: Some(joinsplit_data),
|
|
|
|
..
|
2021-04-19 23:22:25 -07:00
|
|
|
} => Box::new(joinsplit_data.nullifiers()),
|
2020-10-23 15:29:52 -07:00
|
|
|
// JoinSplits with Groth Proofs
|
|
|
|
Transaction::V4 {
|
|
|
|
joinsplit_data: Some(joinsplit_data),
|
|
|
|
..
|
2021-04-19 23:22:25 -07:00
|
|
|
} => Box::new(joinsplit_data.nullifiers()),
|
2020-10-23 15:29:52 -07:00
|
|
|
// No JoinSplits
|
2021-03-03 13:56:41 -08:00
|
|
|
Transaction::V1 { .. }
|
|
|
|
| Transaction::V2 {
|
|
|
|
joinsplit_data: None,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Transaction::V3 {
|
|
|
|
joinsplit_data: None,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Transaction::V4 {
|
|
|
|
joinsplit_data: None,
|
|
|
|
..
|
2021-04-15 15:19:28 -07:00
|
|
|
}
|
|
|
|
| Transaction::V5 { .. } => Box::new(std::iter::empty()),
|
2020-10-23 15:29:52 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-10 08:33:15 -08:00
|
|
|
/// Access the JoinSplit public validating key in this transaction,
|
|
|
|
/// regardless of version, if any.
|
|
|
|
pub fn sprout_joinsplit_pub_key(&self) -> Option<ed25519::VerificationKeyBytes> {
|
|
|
|
match self {
|
|
|
|
// JoinSplits with Bctv14 Proofs
|
|
|
|
Transaction::V2 {
|
|
|
|
joinsplit_data: Some(joinsplit_data),
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Transaction::V3 {
|
|
|
|
joinsplit_data: Some(joinsplit_data),
|
|
|
|
..
|
|
|
|
} => Some(joinsplit_data.pub_key),
|
|
|
|
// JoinSplits with Groth Proofs
|
|
|
|
Transaction::V4 {
|
|
|
|
joinsplit_data: Some(joinsplit_data),
|
|
|
|
..
|
|
|
|
} => Some(joinsplit_data.pub_key),
|
|
|
|
// No JoinSplits
|
|
|
|
Transaction::V1 { .. }
|
|
|
|
| Transaction::V2 {
|
|
|
|
joinsplit_data: None,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Transaction::V3 {
|
|
|
|
joinsplit_data: None,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Transaction::V4 {
|
|
|
|
joinsplit_data: None,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Transaction::V5 { .. } => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-09 08:50:26 -08:00
|
|
|
/// Return if the transaction has any Sprout JoinSplit data.
|
|
|
|
pub fn has_sprout_joinsplit_data(&self) -> bool {
|
|
|
|
match self {
|
|
|
|
// No JoinSplits
|
|
|
|
Transaction::V1 { .. } | Transaction::V5 { .. } => false,
|
|
|
|
|
|
|
|
// JoinSplits-on-BCTV14
|
|
|
|
Transaction::V2 { joinsplit_data, .. } | Transaction::V3 { joinsplit_data, .. } => {
|
|
|
|
joinsplit_data.is_some()
|
|
|
|
}
|
|
|
|
|
|
|
|
// JoinSplits-on-Groth16
|
|
|
|
Transaction::V4 { joinsplit_data, .. } => joinsplit_data.is_some(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the Sprout note commitments in this transaction.
|
|
|
|
pub fn sprout_note_commitments(
|
|
|
|
&self,
|
|
|
|
) -> Box<dyn Iterator<Item = &sprout::commitment::NoteCommitment> + '_> {
|
|
|
|
match self {
|
|
|
|
// Return [`NoteCommitment`]s with [`Bctv14Proof`]s.
|
|
|
|
Transaction::V2 {
|
|
|
|
joinsplit_data: Some(joinsplit_data),
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Transaction::V3 {
|
|
|
|
joinsplit_data: Some(joinsplit_data),
|
|
|
|
..
|
|
|
|
} => Box::new(joinsplit_data.note_commitments()),
|
|
|
|
|
|
|
|
// Return [`NoteCommitment`]s with [`Groth16Proof`]s.
|
|
|
|
Transaction::V4 {
|
|
|
|
joinsplit_data: Some(joinsplit_data),
|
|
|
|
..
|
|
|
|
} => Box::new(joinsplit_data.note_commitments()),
|
|
|
|
|
|
|
|
// Return an empty iterator.
|
|
|
|
Transaction::V2 {
|
|
|
|
joinsplit_data: None,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Transaction::V3 {
|
|
|
|
joinsplit_data: None,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Transaction::V4 {
|
|
|
|
joinsplit_data: None,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Transaction::V1 { .. }
|
|
|
|
| Transaction::V5 { .. } => Box::new(std::iter::empty()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-27 17:43:00 -07:00
|
|
|
// sapling
|
|
|
|
|
2021-11-30 08:05:35 -08:00
|
|
|
/// Access the deduplicated [`sapling::tree::Root`]s in this transaction,
|
|
|
|
/// regardless of version.
|
|
|
|
pub fn sapling_anchors(&self) -> Box<dyn Iterator<Item = sapling::tree::Root> + '_> {
|
|
|
|
// This function returns a boxed iterator because the different
|
|
|
|
// transaction variants end up having different iterator types
|
|
|
|
match self {
|
|
|
|
Transaction::V4 {
|
|
|
|
sapling_shielded_data: Some(sapling_shielded_data),
|
|
|
|
..
|
|
|
|
} => Box::new(sapling_shielded_data.anchors()),
|
|
|
|
|
|
|
|
Transaction::V5 {
|
|
|
|
sapling_shielded_data: Some(sapling_shielded_data),
|
|
|
|
..
|
|
|
|
} => Box::new(sapling_shielded_data.anchors()),
|
|
|
|
|
|
|
|
// No Spends
|
|
|
|
Transaction::V1 { .. }
|
|
|
|
| Transaction::V2 { .. }
|
|
|
|
| Transaction::V3 { .. }
|
|
|
|
| Transaction::V4 {
|
|
|
|
sapling_shielded_data: None,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Transaction::V5 {
|
|
|
|
sapling_shielded_data: None,
|
|
|
|
..
|
|
|
|
} => Box::new(std::iter::empty()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-27 17:43:00 -07:00
|
|
|
/// Iterate over the sapling [`Spend`](sapling::Spend)s for this transaction,
|
|
|
|
/// returning `Spend<PerSpendAnchor>` regardless of the underlying
|
|
|
|
/// transaction version.
|
|
|
|
///
|
2021-06-21 18:41:35 -07:00
|
|
|
/// Shared anchors in V5 transactions are copied into each sapling spend.
|
|
|
|
/// This allows the same code to validate spends from V4 and V5 transactions.
|
|
|
|
///
|
2021-04-27 17:43:00 -07:00
|
|
|
/// # Correctness
|
|
|
|
///
|
|
|
|
/// Do not use this function for serialization.
|
|
|
|
pub fn sapling_spends_per_anchor(
|
|
|
|
&self,
|
|
|
|
) -> Box<dyn Iterator<Item = sapling::Spend<sapling::PerSpendAnchor>> + '_> {
|
|
|
|
match self {
|
|
|
|
Transaction::V4 {
|
|
|
|
sapling_shielded_data: Some(sapling_shielded_data),
|
|
|
|
..
|
|
|
|
} => Box::new(sapling_shielded_data.spends_per_anchor()),
|
|
|
|
Transaction::V5 {
|
|
|
|
sapling_shielded_data: Some(sapling_shielded_data),
|
|
|
|
..
|
|
|
|
} => Box::new(sapling_shielded_data.spends_per_anchor()),
|
|
|
|
|
|
|
|
// No Spends
|
|
|
|
Transaction::V1 { .. }
|
|
|
|
| Transaction::V2 { .. }
|
|
|
|
| Transaction::V3 { .. }
|
|
|
|
| Transaction::V4 {
|
|
|
|
sapling_shielded_data: None,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Transaction::V5 {
|
|
|
|
sapling_shielded_data: None,
|
|
|
|
..
|
|
|
|
} => Box::new(std::iter::empty()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Iterate over the sapling [`Output`](sapling::Output)s for this
|
|
|
|
/// transaction
|
|
|
|
pub fn sapling_outputs(&self) -> Box<dyn Iterator<Item = &sapling::Output> + '_> {
|
|
|
|
match self {
|
|
|
|
Transaction::V4 {
|
|
|
|
sapling_shielded_data: Some(sapling_shielded_data),
|
|
|
|
..
|
|
|
|
} => Box::new(sapling_shielded_data.outputs()),
|
|
|
|
Transaction::V5 {
|
|
|
|
sapling_shielded_data: Some(sapling_shielded_data),
|
|
|
|
..
|
|
|
|
} => Box::new(sapling_shielded_data.outputs()),
|
|
|
|
|
|
|
|
// No Outputs
|
|
|
|
Transaction::V1 { .. }
|
|
|
|
| Transaction::V2 { .. }
|
|
|
|
| Transaction::V3 { .. }
|
|
|
|
| Transaction::V4 {
|
|
|
|
sapling_shielded_data: None,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Transaction::V5 {
|
|
|
|
sapling_shielded_data: None,
|
|
|
|
..
|
|
|
|
} => Box::new(std::iter::empty()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-23 15:29:52 -07:00
|
|
|
/// Access the sapling::Nullifiers in this transaction, regardless of version.
|
|
|
|
pub fn sapling_nullifiers(&self) -> Box<dyn Iterator<Item = &sapling::Nullifier> + '_> {
|
|
|
|
// This function returns a boxed iterator because the different
|
|
|
|
// transaction variants end up having different iterator types
|
|
|
|
match self {
|
2021-04-15 15:19:28 -07:00
|
|
|
// Spends with Groth Proofs
|
2020-10-23 15:29:52 -07:00
|
|
|
Transaction::V4 {
|
2021-03-31 14:34:25 -07:00
|
|
|
sapling_shielded_data: Some(sapling_shielded_data),
|
2020-10-23 15:29:52 -07:00
|
|
|
..
|
2021-03-31 14:34:25 -07:00
|
|
|
} => Box::new(sapling_shielded_data.nullifiers()),
|
2021-04-15 15:19:28 -07:00
|
|
|
Transaction::V5 {
|
|
|
|
sapling_shielded_data: Some(sapling_shielded_data),
|
|
|
|
..
|
|
|
|
} => Box::new(sapling_shielded_data.nullifiers()),
|
|
|
|
|
|
|
|
// No Spends
|
2021-03-03 13:56:41 -08:00
|
|
|
Transaction::V1 { .. }
|
|
|
|
| Transaction::V2 { .. }
|
|
|
|
| Transaction::V3 { .. }
|
|
|
|
| Transaction::V4 {
|
2021-03-31 14:34:25 -07:00
|
|
|
sapling_shielded_data: None,
|
2021-03-03 13:56:41 -08:00
|
|
|
..
|
2021-04-15 15:19:28 -07:00
|
|
|
}
|
|
|
|
| Transaction::V5 {
|
|
|
|
sapling_shielded_data: None,
|
|
|
|
..
|
2021-03-03 13:56:41 -08:00
|
|
|
} => Box::new(std::iter::empty()),
|
2020-10-23 15:29:52 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-18 15:05:52 -08:00
|
|
|
/// Returns the Sapling note commitments in this transaction, regardless of version.
|
2021-07-29 06:37:18 -07:00
|
|
|
pub fn sapling_note_commitments(&self) -> Box<dyn Iterator<Item = &jubjub::Fq> + '_> {
|
|
|
|
// This function returns a boxed iterator because the different
|
|
|
|
// transaction variants end up having different iterator types
|
|
|
|
match self {
|
|
|
|
// Spends with Groth16 Proofs
|
|
|
|
Transaction::V4 {
|
|
|
|
sapling_shielded_data: Some(sapling_shielded_data),
|
|
|
|
..
|
|
|
|
} => Box::new(sapling_shielded_data.note_commitments()),
|
|
|
|
Transaction::V5 {
|
|
|
|
sapling_shielded_data: Some(sapling_shielded_data),
|
|
|
|
..
|
|
|
|
} => Box::new(sapling_shielded_data.note_commitments()),
|
|
|
|
|
|
|
|
// No Spends
|
|
|
|
Transaction::V1 { .. }
|
|
|
|
| Transaction::V2 { .. }
|
|
|
|
| Transaction::V3 { .. }
|
|
|
|
| Transaction::V4 {
|
|
|
|
sapling_shielded_data: None,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Transaction::V5 {
|
|
|
|
sapling_shielded_data: None,
|
|
|
|
..
|
|
|
|
} => Box::new(std::iter::empty()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-11 07:25:48 -07:00
|
|
|
/// Return if the transaction has any Sapling shielded data.
|
|
|
|
pub fn has_sapling_shielded_data(&self) -> bool {
|
|
|
|
match self {
|
|
|
|
Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } => false,
|
|
|
|
Transaction::V4 {
|
|
|
|
sapling_shielded_data,
|
|
|
|
..
|
|
|
|
} => sapling_shielded_data.is_some(),
|
|
|
|
Transaction::V5 {
|
|
|
|
sapling_shielded_data,
|
|
|
|
..
|
|
|
|
} => sapling_shielded_data.is_some(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-01 00:53:13 -07:00
|
|
|
// orchard
|
|
|
|
|
2021-08-07 06:23:32 -07:00
|
|
|
/// Access the [`orchard::ShieldedData`] in this transaction,
|
2021-06-02 18:54:08 -07:00
|
|
|
/// regardless of version.
|
|
|
|
pub fn orchard_shielded_data(&self) -> Option<&orchard::ShieldedData> {
|
2021-06-01 18:32:52 -07:00
|
|
|
match self {
|
2021-06-02 18:54:08 -07:00
|
|
|
// Maybe Orchard shielded data
|
2021-06-01 18:32:52 -07:00
|
|
|
Transaction::V5 {
|
2021-06-02 18:54:08 -07:00
|
|
|
orchard_shielded_data,
|
2021-06-01 18:32:52 -07:00
|
|
|
..
|
2021-06-02 18:54:08 -07:00
|
|
|
} => orchard_shielded_data.as_ref(),
|
2021-06-01 18:32:52 -07:00
|
|
|
|
2021-06-02 18:54:08 -07:00
|
|
|
// No Orchard shielded data
|
2021-06-01 18:32:52 -07:00
|
|
|
Transaction::V1 { .. }
|
|
|
|
| Transaction::V2 { .. }
|
|
|
|
| Transaction::V3 { .. }
|
2021-06-02 18:54:08 -07:00
|
|
|
| Transaction::V4 { .. } => None,
|
2021-06-01 18:32:52 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-07 06:23:32 -07:00
|
|
|
/// Modify the [`orchard::ShieldedData`] in this transaction,
|
|
|
|
/// regardless of version.
|
|
|
|
#[cfg(any(test, feature = "proptest-impl"))]
|
|
|
|
pub fn orchard_shielded_data_mut(&mut self) -> Option<&mut orchard::ShieldedData> {
|
|
|
|
match self {
|
|
|
|
Transaction::V5 {
|
|
|
|
orchard_shielded_data: Some(orchard_shielded_data),
|
|
|
|
..
|
|
|
|
} => Some(orchard_shielded_data),
|
|
|
|
|
|
|
|
Transaction::V1 { .. }
|
|
|
|
| Transaction::V2 { .. }
|
|
|
|
| Transaction::V3 { .. }
|
|
|
|
| Transaction::V4 { .. }
|
|
|
|
| Transaction::V5 {
|
|
|
|
orchard_shielded_data: None,
|
|
|
|
..
|
|
|
|
} => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-02 18:54:08 -07:00
|
|
|
/// Iterate over the [`orchard::Action`]s in this transaction, if there are any,
|
|
|
|
/// regardless of version.
|
|
|
|
pub fn orchard_actions(&self) -> impl Iterator<Item = &orchard::Action> {
|
|
|
|
self.orchard_shielded_data()
|
|
|
|
.into_iter()
|
2021-12-09 06:19:14 -08:00
|
|
|
.flat_map(orchard::ShieldedData::actions)
|
2021-06-02 18:54:08 -07:00
|
|
|
}
|
2021-06-01 00:53:13 -07:00
|
|
|
|
2021-06-02 18:54:08 -07:00
|
|
|
/// Access the [`orchard::Nullifier`]s in this transaction, if there are any,
|
|
|
|
/// regardless of version.
|
|
|
|
pub fn orchard_nullifiers(&self) -> impl Iterator<Item = &orchard::Nullifier> {
|
|
|
|
self.orchard_shielded_data()
|
|
|
|
.into_iter()
|
2021-12-09 06:19:14 -08:00
|
|
|
.flat_map(orchard::ShieldedData::nullifiers)
|
2021-06-01 00:53:13 -07:00
|
|
|
}
|
2021-06-28 15:28:49 -07:00
|
|
|
|
2021-07-29 06:37:18 -07:00
|
|
|
/// Access the note commitments in this transaction, if there are any,
|
|
|
|
/// regardless of version.
|
|
|
|
pub fn orchard_note_commitments(&self) -> impl Iterator<Item = &pallas::Base> {
|
|
|
|
self.orchard_shielded_data()
|
|
|
|
.into_iter()
|
2021-12-09 06:19:14 -08:00
|
|
|
.flat_map(orchard::ShieldedData::note_commitments)
|
2021-07-29 06:37:18 -07:00
|
|
|
}
|
|
|
|
|
2021-06-28 15:28:49 -07:00
|
|
|
/// Access the [`orchard::Flags`] in this transaction, if there is any,
|
|
|
|
/// regardless of version.
|
|
|
|
pub fn orchard_flags(&self) -> Option<orchard::shielded_data::Flags> {
|
|
|
|
self.orchard_shielded_data()
|
|
|
|
.map(|orchard_shielded_data| orchard_shielded_data.flags)
|
|
|
|
}
|
2021-07-28 20:49:36 -07:00
|
|
|
|
2021-08-07 06:23:32 -07:00
|
|
|
/// Return if the transaction has any Orchard shielded data,
|
|
|
|
/// regardless of version.
|
2021-08-03 11:33:51 -07:00
|
|
|
pub fn has_orchard_shielded_data(&self) -> bool {
|
|
|
|
self.orchard_shielded_data().is_some()
|
|
|
|
}
|
|
|
|
|
2021-07-28 20:49:36 -07:00
|
|
|
// value balances
|
|
|
|
|
2021-08-09 10:22:26 -07:00
|
|
|
/// Return the transparent value balance,
|
|
|
|
/// using the outputs spent by this transaction.
|
2021-07-28 20:49:36 -07:00
|
|
|
///
|
2021-08-09 10:22:26 -07:00
|
|
|
/// See `transparent_value_balance` for details.
|
2022-06-27 23:22:07 -07:00
|
|
|
#[allow(clippy::unwrap_in_result)]
|
2021-08-09 10:22:26 -07:00
|
|
|
fn transparent_value_balance_from_outputs(
|
|
|
|
&self,
|
|
|
|
outputs: &HashMap<transparent::OutPoint, transparent::Output>,
|
|
|
|
) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
|
|
|
|
let input_value = self
|
|
|
|
.inputs()
|
|
|
|
.iter()
|
|
|
|
.map(|i| i.value_from_outputs(outputs))
|
|
|
|
.sum::<Result<Amount<NonNegative>, AmountError>>()
|
|
|
|
.map_err(ValueBalanceError::Transparent)?
|
|
|
|
.constrain()
|
|
|
|
.expect("conversion from NonNegative to NegativeAllowed is always valid");
|
|
|
|
|
|
|
|
let output_value = self
|
|
|
|
.outputs()
|
|
|
|
.iter()
|
|
|
|
.map(|o| o.value())
|
|
|
|
.sum::<Result<Amount<NonNegative>, AmountError>>()
|
|
|
|
.map_err(ValueBalanceError::Transparent)?
|
|
|
|
.constrain()
|
|
|
|
.expect("conversion from NonNegative to NegativeAllowed is always valid");
|
|
|
|
|
|
|
|
(input_value - output_value)
|
|
|
|
.map(ValueBalance::from_transparent_amount)
|
|
|
|
.map_err(ValueBalanceError::Transparent)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Modify the transparent output values of this transaction, regardless of version.
|
|
|
|
#[cfg(any(test, feature = "proptest-impl"))]
|
|
|
|
pub fn output_values_mut(&mut self) -> impl Iterator<Item = &mut Amount<NonNegative>> {
|
|
|
|
self.outputs_mut()
|
|
|
|
.iter_mut()
|
|
|
|
.map(|output| &mut output.value)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the `vpub_old` fields from `JoinSplit`s in this transaction,
|
2021-12-13 11:50:49 -08:00
|
|
|
/// regardless of version, in the order they appear in the transaction.
|
2021-08-09 10:22:26 -07:00
|
|
|
///
|
|
|
|
/// These values are added to the sprout chain value pool,
|
|
|
|
/// and removed from the value pool of this transaction.
|
|
|
|
pub fn output_values_to_sprout(&self) -> Box<dyn Iterator<Item = &Amount<NonNegative>> + '_> {
|
|
|
|
match self {
|
|
|
|
// JoinSplits with Bctv14 Proofs
|
|
|
|
Transaction::V2 {
|
|
|
|
joinsplit_data: Some(joinsplit_data),
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Transaction::V3 {
|
|
|
|
joinsplit_data: Some(joinsplit_data),
|
|
|
|
..
|
|
|
|
} => Box::new(
|
|
|
|
joinsplit_data
|
|
|
|
.joinsplits()
|
|
|
|
.map(|joinsplit| &joinsplit.vpub_old),
|
|
|
|
),
|
|
|
|
// JoinSplits with Groth Proofs
|
|
|
|
Transaction::V4 {
|
|
|
|
joinsplit_data: Some(joinsplit_data),
|
|
|
|
..
|
|
|
|
} => Box::new(
|
|
|
|
joinsplit_data
|
|
|
|
.joinsplits()
|
|
|
|
.map(|joinsplit| &joinsplit.vpub_old),
|
|
|
|
),
|
|
|
|
// No JoinSplits
|
|
|
|
Transaction::V1 { .. }
|
|
|
|
| Transaction::V2 {
|
|
|
|
joinsplit_data: None,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Transaction::V3 {
|
|
|
|
joinsplit_data: None,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Transaction::V4 {
|
|
|
|
joinsplit_data: None,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Transaction::V5 { .. } => Box::new(std::iter::empty()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Modify the `vpub_old` fields from `JoinSplit`s in this transaction,
|
2021-12-13 11:50:49 -08:00
|
|
|
/// regardless of version, in the order they appear in the transaction.
|
2021-08-09 10:22:26 -07:00
|
|
|
///
|
|
|
|
/// See `output_values_to_sprout` for details.
|
|
|
|
#[cfg(any(test, feature = "proptest-impl"))]
|
|
|
|
pub fn output_values_to_sprout_mut(
|
|
|
|
&mut self,
|
|
|
|
) -> Box<dyn Iterator<Item = &mut Amount<NonNegative>> + '_> {
|
|
|
|
match self {
|
|
|
|
// JoinSplits with Bctv14 Proofs
|
|
|
|
Transaction::V2 {
|
|
|
|
joinsplit_data: Some(joinsplit_data),
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Transaction::V3 {
|
|
|
|
joinsplit_data: Some(joinsplit_data),
|
|
|
|
..
|
|
|
|
} => Box::new(
|
|
|
|
joinsplit_data
|
|
|
|
.joinsplits_mut()
|
|
|
|
.map(|joinsplit| &mut joinsplit.vpub_old),
|
|
|
|
),
|
2021-12-09 08:50:26 -08:00
|
|
|
// JoinSplits with Groth16 Proofs
|
2021-08-09 10:22:26 -07:00
|
|
|
Transaction::V4 {
|
|
|
|
joinsplit_data: Some(joinsplit_data),
|
|
|
|
..
|
|
|
|
} => Box::new(
|
|
|
|
joinsplit_data
|
|
|
|
.joinsplits_mut()
|
|
|
|
.map(|joinsplit| &mut joinsplit.vpub_old),
|
|
|
|
),
|
|
|
|
// No JoinSplits
|
|
|
|
Transaction::V1 { .. }
|
|
|
|
| Transaction::V2 {
|
|
|
|
joinsplit_data: None,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Transaction::V3 {
|
|
|
|
joinsplit_data: None,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Transaction::V4 {
|
|
|
|
joinsplit_data: None,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Transaction::V5 { .. } => Box::new(std::iter::empty()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the `vpub_new` fields from `JoinSplit`s in this transaction,
|
2021-12-13 11:50:49 -08:00
|
|
|
/// regardless of version, in the order they appear in the transaction.
|
2021-08-09 10:22:26 -07:00
|
|
|
///
|
|
|
|
/// These values are removed from the value pool of this transaction.
|
|
|
|
/// and added to the sprout chain value pool.
|
|
|
|
pub fn input_values_from_sprout(&self) -> Box<dyn Iterator<Item = &Amount<NonNegative>> + '_> {
|
|
|
|
match self {
|
|
|
|
// JoinSplits with Bctv14 Proofs
|
|
|
|
Transaction::V2 {
|
|
|
|
joinsplit_data: Some(joinsplit_data),
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Transaction::V3 {
|
|
|
|
joinsplit_data: Some(joinsplit_data),
|
|
|
|
..
|
|
|
|
} => Box::new(
|
|
|
|
joinsplit_data
|
|
|
|
.joinsplits()
|
|
|
|
.map(|joinsplit| &joinsplit.vpub_new),
|
|
|
|
),
|
|
|
|
// JoinSplits with Groth Proofs
|
|
|
|
Transaction::V4 {
|
|
|
|
joinsplit_data: Some(joinsplit_data),
|
|
|
|
..
|
|
|
|
} => Box::new(
|
|
|
|
joinsplit_data
|
|
|
|
.joinsplits()
|
|
|
|
.map(|joinsplit| &joinsplit.vpub_new),
|
|
|
|
),
|
|
|
|
// No JoinSplits
|
|
|
|
Transaction::V1 { .. }
|
|
|
|
| Transaction::V2 {
|
|
|
|
joinsplit_data: None,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Transaction::V3 {
|
|
|
|
joinsplit_data: None,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Transaction::V4 {
|
|
|
|
joinsplit_data: None,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Transaction::V5 { .. } => Box::new(std::iter::empty()),
|
|
|
|
}
|
|
|
|
}
|
2021-07-28 20:49:36 -07:00
|
|
|
|
2021-08-09 10:22:26 -07:00
|
|
|
/// Modify the `vpub_new` fields from `JoinSplit`s in this transaction,
|
2021-12-13 11:50:49 -08:00
|
|
|
/// regardless of version, in the order they appear in the transaction.
|
2021-08-09 10:22:26 -07:00
|
|
|
///
|
|
|
|
/// See `input_values_from_sprout` for details.
|
|
|
|
#[cfg(any(test, feature = "proptest-impl"))]
|
|
|
|
pub fn input_values_from_sprout_mut(
|
|
|
|
&mut self,
|
|
|
|
) -> Box<dyn Iterator<Item = &mut Amount<NonNegative>> + '_> {
|
|
|
|
match self {
|
|
|
|
// JoinSplits with Bctv14 Proofs
|
|
|
|
Transaction::V2 {
|
|
|
|
joinsplit_data: Some(joinsplit_data),
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Transaction::V3 {
|
|
|
|
joinsplit_data: Some(joinsplit_data),
|
|
|
|
..
|
|
|
|
} => Box::new(
|
|
|
|
joinsplit_data
|
|
|
|
.joinsplits_mut()
|
|
|
|
.map(|joinsplit| &mut joinsplit.vpub_new),
|
|
|
|
),
|
|
|
|
// JoinSplits with Groth Proofs
|
|
|
|
Transaction::V4 {
|
|
|
|
joinsplit_data: Some(joinsplit_data),
|
|
|
|
..
|
|
|
|
} => Box::new(
|
|
|
|
joinsplit_data
|
|
|
|
.joinsplits_mut()
|
|
|
|
.map(|joinsplit| &mut joinsplit.vpub_new),
|
|
|
|
),
|
|
|
|
// No JoinSplits
|
|
|
|
Transaction::V1 { .. }
|
|
|
|
| Transaction::V2 {
|
|
|
|
joinsplit_data: None,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Transaction::V3 {
|
|
|
|
joinsplit_data: None,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Transaction::V4 {
|
|
|
|
joinsplit_data: None,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Transaction::V5 { .. } => Box::new(std::iter::empty()),
|
|
|
|
}
|
2021-07-28 20:49:36 -07:00
|
|
|
}
|
|
|
|
|
2021-08-10 07:42:02 -07:00
|
|
|
/// Return a list of sprout value balances,
|
2022-06-02 08:07:35 -07:00
|
|
|
/// the changes in the transaction value pool due to each sprout `JoinSplit`.
|
2021-08-09 10:22:26 -07:00
|
|
|
///
|
2021-08-10 07:42:02 -07:00
|
|
|
/// Each value balance is the sprout `vpub_new` field, minus the `vpub_old` field.
|
2021-07-28 20:49:36 -07:00
|
|
|
///
|
2022-06-02 08:07:35 -07:00
|
|
|
/// See [`sprout_value_balance`][svb] for details.
|
|
|
|
///
|
|
|
|
/// [svb]: crate::transaction::Transaction::sprout_value_balance
|
2021-08-10 07:42:02 -07:00
|
|
|
fn sprout_joinsplit_value_balances(
|
|
|
|
&self,
|
|
|
|
) -> impl Iterator<Item = ValueBalance<NegativeAllowed>> + '_ {
|
|
|
|
let joinsplit_value_balances = match self {
|
2021-08-09 10:22:26 -07:00
|
|
|
Transaction::V2 {
|
|
|
|
joinsplit_data: Some(joinsplit_data),
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Transaction::V3 {
|
|
|
|
joinsplit_data: Some(joinsplit_data),
|
|
|
|
..
|
2021-08-10 07:42:02 -07:00
|
|
|
} => joinsplit_data.joinsplit_value_balances(),
|
2021-08-09 10:22:26 -07:00
|
|
|
Transaction::V4 {
|
|
|
|
joinsplit_data: Some(joinsplit_data),
|
|
|
|
..
|
2021-08-10 07:42:02 -07:00
|
|
|
} => joinsplit_data.joinsplit_value_balances(),
|
2021-08-09 10:22:26 -07:00
|
|
|
Transaction::V1 { .. }
|
|
|
|
| Transaction::V2 {
|
|
|
|
joinsplit_data: None,
|
|
|
|
..
|
2021-07-28 20:49:36 -07:00
|
|
|
}
|
2021-08-09 10:22:26 -07:00
|
|
|
| Transaction::V3 {
|
|
|
|
joinsplit_data: None,
|
|
|
|
..
|
2021-07-28 20:49:36 -07:00
|
|
|
}
|
2021-08-09 10:22:26 -07:00
|
|
|
| Transaction::V4 {
|
|
|
|
joinsplit_data: None,
|
|
|
|
..
|
|
|
|
}
|
2021-08-10 07:42:02 -07:00
|
|
|
| Transaction::V5 { .. } => Box::new(iter::empty()),
|
2021-07-28 20:49:36 -07:00
|
|
|
};
|
|
|
|
|
2021-08-10 07:42:02 -07:00
|
|
|
joinsplit_value_balances.map(ValueBalance::from_sprout_amount)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Return the sprout value balance,
|
2022-06-02 08:07:35 -07:00
|
|
|
/// the change in the transaction value pool due to sprout `JoinSplit`s.
|
2021-08-10 07:42:02 -07:00
|
|
|
///
|
|
|
|
/// The sum of all sprout `vpub_new` fields, minus the sum of all `vpub_old` fields.
|
|
|
|
///
|
|
|
|
/// Positive values are added to this transaction's value pool,
|
|
|
|
/// and removed from the sprout chain value pool.
|
|
|
|
/// Negative values are removed from this transaction,
|
|
|
|
/// and added to the sprout pool.
|
|
|
|
///
|
2022-05-30 13:12:11 -07:00
|
|
|
/// <https://zebra.zfnd.org/dev/rfcs/0012-value-pools.html#definitions>
|
2021-08-10 07:42:02 -07:00
|
|
|
fn sprout_value_balance(&self) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
|
|
|
|
self.sprout_joinsplit_value_balances().sum()
|
2021-07-28 20:49:36 -07:00
|
|
|
}
|
|
|
|
|
2021-08-09 10:22:26 -07:00
|
|
|
/// Return the sapling value balance,
|
|
|
|
/// the change in the transaction value pool due to sapling `Spend`s and `Output`s.
|
2021-07-28 20:49:36 -07:00
|
|
|
///
|
2021-08-09 10:22:26 -07:00
|
|
|
/// Returns the `valueBalanceSapling` field in this transaction.
|
|
|
|
///
|
|
|
|
/// Positive values are added to this transaction's value pool,
|
|
|
|
/// and removed from the sapling chain value pool.
|
|
|
|
/// Negative values are removed from this transaction,
|
|
|
|
/// and added to sapling pool.
|
2021-07-28 20:49:36 -07:00
|
|
|
///
|
2022-05-30 13:12:11 -07:00
|
|
|
/// <https://zebra.zfnd.org/dev/rfcs/0012-value-pools.html#definitions>
|
2021-11-23 19:36:17 -08:00
|
|
|
pub fn sapling_value_balance(&self) -> ValueBalance<NegativeAllowed> {
|
2021-08-09 10:22:26 -07:00
|
|
|
let sapling_value_balance = match self {
|
2021-07-28 20:49:36 -07:00
|
|
|
Transaction::V4 {
|
2021-08-09 10:22:26 -07:00
|
|
|
sapling_shielded_data: Some(sapling_shielded_data),
|
2021-07-28 20:49:36 -07:00
|
|
|
..
|
2021-08-09 10:22:26 -07:00
|
|
|
} => sapling_shielded_data.value_balance,
|
2021-07-28 20:49:36 -07:00
|
|
|
Transaction::V5 {
|
2021-08-09 10:22:26 -07:00
|
|
|
sapling_shielded_data: Some(sapling_shielded_data),
|
|
|
|
..
|
|
|
|
} => sapling_shielded_data.value_balance,
|
|
|
|
|
|
|
|
Transaction::V1 { .. }
|
|
|
|
| Transaction::V2 { .. }
|
|
|
|
| Transaction::V3 { .. }
|
|
|
|
| Transaction::V4 {
|
|
|
|
sapling_shielded_data: None,
|
2021-07-28 20:49:36 -07:00
|
|
|
..
|
2021-08-09 10:22:26 -07:00
|
|
|
}
|
|
|
|
| Transaction::V5 {
|
|
|
|
sapling_shielded_data: None,
|
|
|
|
..
|
|
|
|
} => Amount::zero(),
|
2021-07-28 20:49:36 -07:00
|
|
|
};
|
|
|
|
|
2021-08-09 10:22:26 -07:00
|
|
|
ValueBalance::from_sapling_amount(sapling_value_balance)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Modify the `value_balance` field from the `sapling::ShieldedData` in this transaction,
|
|
|
|
/// regardless of version.
|
|
|
|
///
|
|
|
|
/// See `sapling_value_balance` for details.
|
|
|
|
#[cfg(any(test, feature = "proptest-impl"))]
|
|
|
|
pub fn sapling_value_balance_mut(&mut self) -> Option<&mut Amount<NegativeAllowed>> {
|
|
|
|
match self {
|
|
|
|
Transaction::V4 {
|
|
|
|
sapling_shielded_data: Some(sapling_shielded_data),
|
|
|
|
..
|
|
|
|
} => Some(&mut sapling_shielded_data.value_balance),
|
|
|
|
Transaction::V5 {
|
|
|
|
sapling_shielded_data: Some(sapling_shielded_data),
|
|
|
|
..
|
|
|
|
} => Some(&mut sapling_shielded_data.value_balance),
|
|
|
|
Transaction::V1 { .. }
|
|
|
|
| Transaction::V2 { .. }
|
|
|
|
| Transaction::V3 { .. }
|
|
|
|
| Transaction::V4 {
|
|
|
|
sapling_shielded_data: None,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Transaction::V5 {
|
|
|
|
sapling_shielded_data: None,
|
|
|
|
..
|
|
|
|
} => None,
|
|
|
|
}
|
2021-07-28 20:49:36 -07:00
|
|
|
}
|
|
|
|
|
2022-06-02 08:07:35 -07:00
|
|
|
/// Return the orchard value balance, the change in the transaction value
|
|
|
|
/// pool due to [`orchard::Action`]s.
|
2021-08-09 10:22:26 -07:00
|
|
|
///
|
|
|
|
/// Returns the `valueBalanceOrchard` field in this transaction.
|
2021-07-28 20:49:36 -07:00
|
|
|
///
|
2021-08-09 10:22:26 -07:00
|
|
|
/// Positive values are added to this transaction's value pool,
|
|
|
|
/// and removed from the orchard chain value pool.
|
|
|
|
/// Negative values are removed from this transaction,
|
|
|
|
/// and added to orchard pool.
|
2021-07-28 20:49:36 -07:00
|
|
|
///
|
2022-05-30 13:12:11 -07:00
|
|
|
/// <https://zebra.zfnd.org/dev/rfcs/0012-value-pools.html#definitions>
|
2021-11-23 19:36:17 -08:00
|
|
|
pub fn orchard_value_balance(&self) -> ValueBalance<NegativeAllowed> {
|
2021-08-09 10:22:26 -07:00
|
|
|
let orchard_value_balance = self
|
2021-07-28 20:49:36 -07:00
|
|
|
.orchard_shielded_data()
|
2021-08-09 10:22:26 -07:00
|
|
|
.map(|shielded_data| shielded_data.value_balance)
|
|
|
|
.unwrap_or_else(Amount::zero);
|
|
|
|
|
|
|
|
ValueBalance::from_orchard_amount(orchard_value_balance)
|
|
|
|
}
|
2021-07-28 20:49:36 -07:00
|
|
|
|
2021-08-09 10:22:26 -07:00
|
|
|
/// Modify the `value_balance` field from the `orchard::ShieldedData` in this transaction,
|
|
|
|
/// regardless of version.
|
|
|
|
///
|
|
|
|
/// See `orchard_value_balance` for details.
|
|
|
|
#[cfg(any(test, feature = "proptest-impl"))]
|
|
|
|
pub fn orchard_value_balance_mut(&mut self) -> Option<&mut Amount<NegativeAllowed>> {
|
|
|
|
self.orchard_shielded_data_mut()
|
|
|
|
.map(|shielded_data| &mut shielded_data.value_balance)
|
2021-07-28 20:49:36 -07:00
|
|
|
}
|
|
|
|
|
2021-08-09 10:22:26 -07:00
|
|
|
/// Get the value balances for this transaction,
|
|
|
|
/// using the transparent outputs spent in this transaction.
|
|
|
|
///
|
|
|
|
/// See `value_balance` for details.
|
|
|
|
pub(crate) fn value_balance_from_outputs(
|
|
|
|
&self,
|
|
|
|
outputs: &HashMap<transparent::OutPoint, transparent::Output>,
|
|
|
|
) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
|
|
|
|
self.transparent_value_balance_from_outputs(outputs)?
|
|
|
|
+ self.sprout_value_balance()?
|
|
|
|
+ self.sapling_value_balance()
|
|
|
|
+ self.orchard_value_balance()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get the value balances for this transaction.
|
|
|
|
/// These are the changes in the transaction value pool,
|
|
|
|
/// split up into transparent, sprout, sapling, and orchard values.
|
|
|
|
///
|
|
|
|
/// Calculated as the sum of the inputs and outputs from each pool,
|
|
|
|
/// or the sum of the value balances from each pool.
|
|
|
|
///
|
|
|
|
/// Positive values are added to this transaction's value pool,
|
|
|
|
/// and removed from the corresponding chain value pool.
|
|
|
|
/// Negative values are removed from this transaction,
|
|
|
|
/// and added to the corresponding pool.
|
|
|
|
///
|
2022-05-30 13:12:11 -07:00
|
|
|
/// <https://zebra.zfnd.org/dev/rfcs/0012-value-pools.html#definitions>
|
2021-07-28 20:49:36 -07:00
|
|
|
///
|
|
|
|
/// `utxos` must contain the utxos of every input in the transaction,
|
|
|
|
/// including UTXOs created by earlier transactions in this block.
|
2021-08-09 10:22:26 -07:00
|
|
|
///
|
|
|
|
/// Note: the chain value pool has the opposite sign to the transaction
|
|
|
|
/// value pool.
|
2021-07-28 20:49:36 -07:00
|
|
|
pub fn value_balance(
|
|
|
|
&self,
|
|
|
|
utxos: &HashMap<transparent::OutPoint, transparent::Utxo>,
|
2021-08-09 10:22:26 -07:00
|
|
|
) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
|
|
|
|
self.value_balance_from_outputs(&outputs_from_utxos(utxos.clone()))
|
2021-07-28 20:49:36 -07:00
|
|
|
}
|
2020-02-09 20:55:52 -08:00
|
|
|
}
|