2020-11-19 19:06:10 -08:00
|
|
|
|
//! Transaction checks.
|
|
|
|
|
//!
|
|
|
|
|
//! Code in this file can freely assume that no pre-V4 transactions are present.
|
|
|
|
|
|
2021-10-27 19:49:28 -07:00
|
|
|
|
use std::{borrow::Cow, collections::HashSet, convert::TryFrom, hash::Hash};
|
|
|
|
|
|
2021-11-22 21:53:53 -08:00
|
|
|
|
use chrono::{DateTime, Utc};
|
|
|
|
|
|
2020-10-16 14:40:00 -07:00
|
|
|
|
use zebra_chain::{
|
2021-07-01 16:03:34 -07:00
|
|
|
|
amount::{Amount, NonNegative},
|
|
|
|
|
block::Height,
|
2021-06-02 18:54:08 -07:00
|
|
|
|
orchard::Flags,
|
2021-07-01 16:03:34 -07:00
|
|
|
|
parameters::{Network, NetworkUpgrade},
|
2021-11-11 14:18:37 -08:00
|
|
|
|
primitives::zcash_note_encryption,
|
2021-11-22 21:53:53 -08:00
|
|
|
|
transaction::{LockTime, Transaction},
|
2020-10-16 14:40:00 -07:00
|
|
|
|
};
|
|
|
|
|
|
2020-10-26 23:42:27 -07:00
|
|
|
|
use crate::error::TransactionError;
|
2020-10-16 14:40:00 -07:00
|
|
|
|
|
2021-11-22 21:53:53 -08:00
|
|
|
|
/// Checks if the transaction's lock time allows this transaction to be included in a block.
|
|
|
|
|
///
|
2023-01-31 12:42:11 -08:00
|
|
|
|
/// Arguments:
|
|
|
|
|
/// - `block_height`: the height of the mined block, or the height of the next block for mempool
|
|
|
|
|
/// transactions
|
|
|
|
|
/// - `block_time`: the time in the mined block header, or the median-time-past of the next block
|
|
|
|
|
/// for the mempool. Optional if the lock time is a height.
|
|
|
|
|
///
|
|
|
|
|
/// # Panics
|
|
|
|
|
///
|
|
|
|
|
/// If the lock time is a time, and `block_time` is `None`.
|
|
|
|
|
///
|
|
|
|
|
/// # Consensus
|
2021-11-22 21:53:53 -08:00
|
|
|
|
///
|
|
|
|
|
/// > 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.
|
|
|
|
|
///
|
|
|
|
|
/// [`Transaction::lock_time`] validates the transparent input sequence numbers, returning [`None`]
|
2023-01-31 12:42:11 -08:00
|
|
|
|
/// if they indicate that the transaction is finalized by them.
|
|
|
|
|
/// Otherwise, this function checks that the lock time is in the past.
|
|
|
|
|
///
|
|
|
|
|
/// ## Mempool Consensus for Block Templates
|
|
|
|
|
///
|
|
|
|
|
/// > the nTime field MUST represent a time strictly greater than the median of the
|
|
|
|
|
/// > timestamps of the past PoWMedianBlockSpan blocks.
|
|
|
|
|
/// <https://zips.z.cash/protocol/protocol.pdf#blockheader>
|
|
|
|
|
///
|
|
|
|
|
/// > The transaction can be added to any block whose block time is greater than the locktime.
|
|
|
|
|
/// <https://developer.bitcoin.org/devguide/transactions.html#locktime-and-sequence-number>
|
|
|
|
|
///
|
|
|
|
|
/// If the transaction's lock time is less than the median-time-past,
|
|
|
|
|
/// it will always be less than the next block's time,
|
|
|
|
|
/// because the next block's time is strictly greater than the median-time-past.
|
|
|
|
|
/// (That is, `lock-time < median-time-past < block-header-time`.)
|
|
|
|
|
///
|
|
|
|
|
/// Using `median-time-past + 1s` (the next block's mintime) would also satisfy this consensus rule,
|
|
|
|
|
/// but we prefer the rule implemented by `zcashd`'s mempool:
|
|
|
|
|
/// <https://github.com/zcash/zcash/blob/9e1efad2d13dca5ee094a38e6aa25b0f2464da94/src/main.cpp#L776-L784>
|
2021-11-22 21:53:53 -08:00
|
|
|
|
pub fn lock_time_has_passed(
|
|
|
|
|
tx: &Transaction,
|
|
|
|
|
block_height: Height,
|
2023-01-31 12:42:11 -08:00
|
|
|
|
block_time: impl Into<Option<DateTime<Utc>>>,
|
2021-11-22 21:53:53 -08:00
|
|
|
|
) -> Result<(), TransactionError> {
|
|
|
|
|
match tx.lock_time() {
|
|
|
|
|
Some(LockTime::Height(unlock_height)) => {
|
|
|
|
|
// > The transaction can be added to any block which has a greater height.
|
|
|
|
|
// The Bitcoin documentation is wrong or outdated here,
|
|
|
|
|
// so this code is based on the `zcashd` implementation at:
|
|
|
|
|
// https://github.com/zcash/zcash/blob/1a7c2a3b04bcad6549be6d571bfdff8af9a2c814/src/main.cpp#L722
|
|
|
|
|
if block_height > unlock_height {
|
|
|
|
|
Ok(())
|
|
|
|
|
} else {
|
|
|
|
|
Err(TransactionError::LockedUntilAfterBlockHeight(unlock_height))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Some(LockTime::Time(unlock_time)) => {
|
|
|
|
|
// > The transaction can be added to any block whose block time is greater than the locktime.
|
|
|
|
|
// https://developer.bitcoin.org/devguide/transactions.html#locktime-and-sequence-number
|
2023-01-31 12:42:11 -08:00
|
|
|
|
let block_time = block_time
|
|
|
|
|
.into()
|
|
|
|
|
.expect("time must be provided if LockTime is a time");
|
|
|
|
|
|
2021-11-22 21:53:53 -08:00
|
|
|
|
if block_time > unlock_time {
|
|
|
|
|
Ok(())
|
|
|
|
|
} else {
|
|
|
|
|
Err(TransactionError::LockedUntilAfterBlockTime(unlock_time))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
None => Ok(()),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-19 19:13:52 -08:00
|
|
|
|
/// Checks that the transaction has inputs and outputs.
|
|
|
|
|
///
|
2022-02-08 12:28:40 -08:00
|
|
|
|
/// # Consensus
|
|
|
|
|
///
|
2021-04-27 17:43:00 -07:00
|
|
|
|
/// For `Transaction::V4`:
|
2022-02-08 12:28:40 -08:00
|
|
|
|
///
|
|
|
|
|
/// > [Sapling onward] If effectiveVersion < 5, then at least one of
|
|
|
|
|
/// > tx_in_count, nSpendsSapling, and nJoinSplit MUST be nonzero.
|
|
|
|
|
///
|
|
|
|
|
/// > [Sapling onward] If effectiveVersion < 5, then at least one of
|
|
|
|
|
/// > tx_out_count, nOutputsSapling, and nJoinSplit MUST be nonzero.
|
2020-11-19 19:13:52 -08:00
|
|
|
|
///
|
2021-04-27 17:43:00 -07:00
|
|
|
|
/// For `Transaction::V5`:
|
|
|
|
|
///
|
2022-02-08 12:28:40 -08:00
|
|
|
|
/// > [NU5 onward] If effectiveVersion >= 5 then this condition MUST hold:
|
|
|
|
|
/// > tx_in_count > 0 or nSpendsSapling > 0 or (nActionsOrchard > 0 and enableSpendsOrchard = 1).
|
|
|
|
|
///
|
|
|
|
|
/// > [NU5 onward] If effectiveVersion >= 5 then this condition MUST hold:
|
|
|
|
|
/// > tx_out_count > 0 or nOutputsSapling > 0 or (nActionsOrchard > 0 and enableOutputsOrchard = 1).
|
2020-10-16 14:40:00 -07:00
|
|
|
|
///
|
2022-02-08 12:28:40 -08:00
|
|
|
|
/// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
|
|
|
|
|
///
|
|
|
|
|
/// This check counts both `Coinbase` and `PrevOut` transparent inputs.
|
2020-11-19 19:13:52 -08:00
|
|
|
|
pub fn has_inputs_and_outputs(tx: &Transaction) -> Result<(), TransactionError> {
|
2021-07-28 21:23:50 -07:00
|
|
|
|
if !tx.has_transparent_or_shielded_inputs() {
|
2021-04-27 17:43:00 -07:00
|
|
|
|
Err(TransactionError::NoInputs)
|
2021-07-28 21:23:50 -07:00
|
|
|
|
} else if !tx.has_transparent_or_shielded_outputs() {
|
2021-04-27 17:43:00 -07:00
|
|
|
|
Err(TransactionError::NoOutputs)
|
|
|
|
|
} else {
|
|
|
|
|
Ok(())
|
2020-10-16 14:40:00 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-08 13:45:54 -08:00
|
|
|
|
/// Checks that the transaction has enough orchard flags.
|
|
|
|
|
///
|
2022-02-08 12:28:40 -08:00
|
|
|
|
/// # Consensus
|
|
|
|
|
///
|
2021-11-08 13:45:54 -08:00
|
|
|
|
/// For `Transaction::V5` only:
|
|
|
|
|
///
|
2022-02-08 12:28:40 -08:00
|
|
|
|
/// > [NU5 onward] If effectiveVersion >= 5 and nActionsOrchard > 0, then at least one of enableSpendsOrchard and enableOutputsOrchard MUST be 1.
|
|
|
|
|
///
|
|
|
|
|
/// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
|
2021-11-08 13:45:54 -08:00
|
|
|
|
pub fn has_enough_orchard_flags(tx: &Transaction) -> Result<(), TransactionError> {
|
|
|
|
|
if !tx.has_enough_orchard_flags() {
|
|
|
|
|
return Err(TransactionError::NotEnoughFlags);
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-27 17:43:00 -07:00
|
|
|
|
/// Check that a coinbase transaction has no PrevOut inputs, JoinSplits, or spends.
|
|
|
|
|
///
|
2022-02-07 18:20:08 -08:00
|
|
|
|
/// # Consensus
|
2021-04-27 17:43:00 -07:00
|
|
|
|
///
|
2022-04-20 02:31:12 -07:00
|
|
|
|
/// > A coinbase transaction MUST NOT have any JoinSplit descriptions.
|
|
|
|
|
///
|
|
|
|
|
/// > A coinbase transaction MUST NOT have any Spend descriptions.
|
2022-02-07 18:20:08 -08:00
|
|
|
|
///
|
|
|
|
|
/// > [NU5 onward] In a version 5 coinbase transaction, the enableSpendsOrchard flag MUST be 0.
|
2021-04-27 17:43:00 -07:00
|
|
|
|
///
|
|
|
|
|
/// This check only counts `PrevOut` transparent inputs.
|
2020-10-13 23:10:18 -07:00
|
|
|
|
///
|
2022-02-07 18:20:08 -08:00
|
|
|
|
/// > [Pre-Heartwood] A coinbase transaction also MUST NOT have any Output descriptions.
|
|
|
|
|
///
|
|
|
|
|
/// Zebra does not validate this last rule explicitly because we checkpoint until Canopy activation.
|
|
|
|
|
///
|
|
|
|
|
/// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
|
2021-04-27 17:43:00 -07:00
|
|
|
|
pub fn coinbase_tx_no_prevout_joinsplit_spend(tx: &Transaction) -> Result<(), TransactionError> {
|
2022-04-20 02:31:12 -07:00
|
|
|
|
if tx.is_coinbase() {
|
|
|
|
|
if tx.joinsplit_count() > 0 {
|
2021-04-27 17:43:00 -07:00
|
|
|
|
return Err(TransactionError::CoinbaseHasJoinSplit);
|
|
|
|
|
} else if tx.sapling_spends_per_anchor().count() > 0 {
|
|
|
|
|
return Err(TransactionError::CoinbaseHasSpend);
|
2020-10-13 23:10:18 -07:00
|
|
|
|
}
|
2021-04-27 17:43:00 -07:00
|
|
|
|
|
2021-06-02 18:54:08 -07:00
|
|
|
|
if let Some(orchard_shielded_data) = tx.orchard_shielded_data() {
|
|
|
|
|
if orchard_shielded_data.flags.contains(Flags::ENABLE_SPENDS) {
|
|
|
|
|
return Err(TransactionError::CoinbaseHasEnableSpendsOrchard);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-10-13 23:10:18 -07:00
|
|
|
|
}
|
2021-04-27 17:43:00 -07:00
|
|
|
|
|
|
|
|
|
Ok(())
|
2020-10-13 23:10:18 -07:00
|
|
|
|
}
|
2021-03-24 09:28:25 -07:00
|
|
|
|
|
2021-12-13 11:50:49 -08:00
|
|
|
|
/// Check if JoinSplits in the transaction have one of its v_{pub} values equal
|
|
|
|
|
/// to zero.
|
|
|
|
|
///
|
|
|
|
|
/// <https://zips.z.cash/protocol/protocol.pdf#joinsplitdesc>
|
|
|
|
|
pub fn joinsplit_has_vpub_zero(tx: &Transaction) -> Result<(), TransactionError> {
|
|
|
|
|
let zero = Amount::<NonNegative>::try_from(0).expect("an amount of 0 is always valid");
|
|
|
|
|
|
|
|
|
|
let vpub_pairs = tx
|
|
|
|
|
.output_values_to_sprout()
|
|
|
|
|
.zip(tx.input_values_from_sprout());
|
|
|
|
|
for (vpub_old, vpub_new) in vpub_pairs {
|
2022-02-08 01:57:09 -08:00
|
|
|
|
// # Consensus
|
|
|
|
|
//
|
2021-12-13 11:50:49 -08:00
|
|
|
|
// > Either v_{pub}^{old} or v_{pub}^{new} MUST be zero.
|
2022-02-08 01:57:09 -08:00
|
|
|
|
//
|
2021-12-13 11:50:49 -08:00
|
|
|
|
// https://zips.z.cash/protocol/protocol.pdf#joinsplitdesc
|
|
|
|
|
if *vpub_old != zero && *vpub_new != zero {
|
|
|
|
|
return Err(TransactionError::BothVPubsNonZero);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-01 16:03:34 -07:00
|
|
|
|
/// Check if a transaction is adding to the sprout pool after Canopy
|
|
|
|
|
/// network upgrade given a block height and a network.
|
|
|
|
|
///
|
2022-05-30 13:12:11 -07:00
|
|
|
|
/// <https://zips.z.cash/zip-0211>
|
|
|
|
|
/// <https://zips.z.cash/protocol/protocol.pdf#joinsplitdesc>
|
2021-07-01 16:03:34 -07:00
|
|
|
|
pub fn disabled_add_to_sprout_pool(
|
|
|
|
|
tx: &Transaction,
|
|
|
|
|
height: Height,
|
|
|
|
|
network: Network,
|
|
|
|
|
) -> Result<(), TransactionError> {
|
|
|
|
|
let canopy_activation_height = NetworkUpgrade::Canopy
|
|
|
|
|
.activation_height(network)
|
|
|
|
|
.expect("Canopy activation height must be present for both networks");
|
|
|
|
|
|
2022-02-08 01:57:09 -08:00
|
|
|
|
// # Consensus
|
|
|
|
|
//
|
|
|
|
|
// > [Canopy onward]: `vpub_old` MUST be zero.
|
|
|
|
|
//
|
2021-07-01 16:03:34 -07:00
|
|
|
|
// https://zips.z.cash/protocol/protocol.pdf#joinsplitdesc
|
|
|
|
|
if height >= canopy_activation_height {
|
|
|
|
|
let zero = Amount::<NonNegative>::try_from(0).expect("an amount of 0 is always valid");
|
|
|
|
|
|
2021-08-09 10:22:26 -07:00
|
|
|
|
let tx_sprout_pool = tx.output_values_to_sprout();
|
2021-07-01 16:03:34 -07:00
|
|
|
|
for vpub_old in tx_sprout_pool {
|
|
|
|
|
if *vpub_old != zero {
|
|
|
|
|
return Err(TransactionError::DisabledAddToSproutPool);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2021-10-27 19:49:28 -07:00
|
|
|
|
|
|
|
|
|
/// Check if a transaction has any internal spend conflicts.
|
|
|
|
|
///
|
|
|
|
|
/// An internal spend conflict happens if the transaction spends a UTXO more than once or if it
|
|
|
|
|
/// reveals a nullifier more than once.
|
|
|
|
|
///
|
|
|
|
|
/// Consensus rules:
|
|
|
|
|
///
|
|
|
|
|
/// "each output of a particular transaction
|
|
|
|
|
/// can only be used as an input once in the block chain.
|
|
|
|
|
/// Any subsequent reference is a forbidden double spend-
|
|
|
|
|
/// an attempt to spend the same satoshis twice."
|
|
|
|
|
///
|
2022-05-30 13:12:11 -07:00
|
|
|
|
/// <https://developer.bitcoin.org/devguide/block_chain.html#introduction>
|
2021-10-27 19:49:28 -07:00
|
|
|
|
///
|
|
|
|
|
/// A _nullifier_ *MUST NOT* repeat either within a _transaction_, or across _transactions_ in a
|
|
|
|
|
/// _valid blockchain_ . *Sprout* and *Sapling* and *Orchard* _nulliers_ are considered disjoint,
|
|
|
|
|
/// even if they have the same bit pattern.
|
|
|
|
|
///
|
2022-05-30 13:12:11 -07:00
|
|
|
|
/// <https://zips.z.cash/protocol/protocol.pdf#nullifierset>
|
2021-10-27 19:49:28 -07:00
|
|
|
|
pub fn spend_conflicts(transaction: &Transaction) -> Result<(), TransactionError> {
|
|
|
|
|
use crate::error::TransactionError::*;
|
|
|
|
|
|
|
|
|
|
let transparent_outpoints = transaction.spent_outpoints().map(Cow::Owned);
|
|
|
|
|
let sprout_nullifiers = transaction.sprout_nullifiers().map(Cow::Borrowed);
|
|
|
|
|
let sapling_nullifiers = transaction.sapling_nullifiers().map(Cow::Borrowed);
|
|
|
|
|
let orchard_nullifiers = transaction.orchard_nullifiers().map(Cow::Borrowed);
|
|
|
|
|
|
|
|
|
|
check_for_duplicates(transparent_outpoints, DuplicateTransparentSpend)?;
|
|
|
|
|
check_for_duplicates(sprout_nullifiers, DuplicateSproutNullifier)?;
|
|
|
|
|
check_for_duplicates(sapling_nullifiers, DuplicateSaplingNullifier)?;
|
|
|
|
|
check_for_duplicates(orchard_nullifiers, DuplicateOrchardNullifier)?;
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Check for duplicate items in a collection.
|
|
|
|
|
///
|
|
|
|
|
/// Each item should be wrapped by a [`Cow`] instance so that this helper function can properly
|
|
|
|
|
/// handle borrowed items and owned items.
|
|
|
|
|
///
|
|
|
|
|
/// If a duplicate is found, an error created by the `error_wrapper` is returned.
|
|
|
|
|
fn check_for_duplicates<'t, T>(
|
|
|
|
|
items: impl IntoIterator<Item = Cow<'t, T>>,
|
|
|
|
|
error_wrapper: impl FnOnce(T) -> TransactionError,
|
|
|
|
|
) -> Result<(), TransactionError>
|
|
|
|
|
where
|
|
|
|
|
T: Clone + Eq + Hash + 't,
|
|
|
|
|
{
|
|
|
|
|
let mut hash_set = HashSet::new();
|
|
|
|
|
|
|
|
|
|
for item in items {
|
|
|
|
|
if let Some(duplicate) = hash_set.replace(item) {
|
|
|
|
|
return Err(error_wrapper(duplicate.into_owned()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2021-11-11 14:18:37 -08:00
|
|
|
|
|
|
|
|
|
/// Checks compatibility with [ZIP-212] shielded Sapling and Orchard coinbase output decryption
|
|
|
|
|
///
|
|
|
|
|
/// Pre-Heartwood: returns `Ok`.
|
|
|
|
|
/// Heartwood-onward: returns `Ok` if all Sapling or Orchard outputs, if any, decrypt successfully with
|
|
|
|
|
/// an all-zeroes outgoing viewing key. Returns `Err` otherwise.
|
|
|
|
|
///
|
|
|
|
|
/// This is used to validate coinbase transactions:
|
|
|
|
|
///
|
2022-02-07 18:20:08 -08:00
|
|
|
|
/// # Consensus
|
|
|
|
|
///
|
2021-11-11 14:18:37 -08:00
|
|
|
|
/// > [Heartwood onward] All Sapling and Orchard outputs in coinbase transactions MUST decrypt to a note
|
|
|
|
|
/// > plaintext, i.e. the procedure in § 4.19.3 ‘Decryption using a Full Viewing Key ( Sapling and Orchard )’ on p. 67
|
|
|
|
|
/// > does not return ⊥, using a sequence of 32 zero bytes as the outgoing viewing key. (This implies that before
|
|
|
|
|
/// > Canopy activation, Sapling outputs of a coinbase transaction MUST have note plaintext lead byte equal to
|
|
|
|
|
/// > 0x01.)
|
|
|
|
|
///
|
|
|
|
|
/// > [Canopy onward] Any Sapling or Orchard output of a coinbase transaction decrypted to a note plaintext
|
|
|
|
|
/// > according to the preceding rule MUST have note plaintext lead byte equal to 0x02. (This applies even during
|
|
|
|
|
/// > the "grace period" specified in [ZIP-212].)
|
|
|
|
|
///
|
2022-02-07 18:20:08 -08:00
|
|
|
|
/// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
|
|
|
|
|
///
|
2021-11-11 14:18:37 -08:00
|
|
|
|
/// [ZIP-212]: https://zips.z.cash/zip-0212#consensus-rule-change-for-coinbase-transactions
|
|
|
|
|
///
|
|
|
|
|
/// TODO: Currently, a 0x01 lead byte is allowed in the "grace period" mentioned since we're
|
|
|
|
|
/// using `librustzcash` to implement this and it doesn't currently allow changing that behavior.
|
2022-05-30 13:12:11 -07:00
|
|
|
|
/// <https://github.com/ZcashFoundation/zebra/issues/3027>
|
2021-11-11 14:18:37 -08:00
|
|
|
|
pub fn coinbase_outputs_are_decryptable(
|
|
|
|
|
transaction: &Transaction,
|
|
|
|
|
network: Network,
|
|
|
|
|
height: Height,
|
|
|
|
|
) -> Result<(), TransactionError> {
|
|
|
|
|
// The consensus rule only applies to Heartwood onward.
|
|
|
|
|
if height
|
|
|
|
|
< NetworkUpgrade::Heartwood
|
|
|
|
|
.activation_height(network)
|
|
|
|
|
.expect("Heartwood height is known")
|
|
|
|
|
{
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !zcash_note_encryption::decrypts_successfully(transaction, network, height) {
|
|
|
|
|
return Err(TransactionError::CoinbaseOutputsNotDecryptable);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2021-11-25 16:37:24 -08:00
|
|
|
|
|
|
|
|
|
/// Returns `Ok(())` if the expiry height for the coinbase transaction is valid
|
|
|
|
|
/// according to specifications [7.1] and [ZIP-203].
|
|
|
|
|
///
|
2022-05-30 13:12:11 -07:00
|
|
|
|
/// [7.1]: https://zips.z.cash/protocol/protocol.pdf#txnencodingandconsensus
|
|
|
|
|
/// [ZIP-203]: https://zips.z.cash/zip-0203
|
2021-11-25 16:37:24 -08:00
|
|
|
|
pub fn coinbase_expiry_height(
|
|
|
|
|
block_height: &Height,
|
|
|
|
|
coinbase: &Transaction,
|
|
|
|
|
network: Network,
|
|
|
|
|
) -> Result<(), TransactionError> {
|
|
|
|
|
let expiry_height = coinbase.expiry_height();
|
|
|
|
|
|
2022-02-10 16:32:57 -08:00
|
|
|
|
// TODO: replace `if let` with `expect` after NU5 mainnet activation
|
|
|
|
|
if let Some(nu5_activation_height) = NetworkUpgrade::Nu5.activation_height(network) {
|
|
|
|
|
// # Consensus
|
|
|
|
|
//
|
|
|
|
|
// > [NU5 onward] The nExpiryHeight field of a coinbase transaction
|
|
|
|
|
// > MUST be equal to its block height.
|
|
|
|
|
//
|
|
|
|
|
// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
|
|
|
|
|
if *block_height >= nu5_activation_height {
|
|
|
|
|
if expiry_height != Some(*block_height) {
|
|
|
|
|
return Err(TransactionError::CoinbaseExpiryBlockHeight {
|
|
|
|
|
expiry_height,
|
|
|
|
|
block_height: *block_height,
|
|
|
|
|
transaction_hash: coinbase.hash(),
|
|
|
|
|
});
|
|
|
|
|
} else {
|
2021-11-25 16:37:24 -08:00
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-02-10 16:32:57 -08:00
|
|
|
|
|
|
|
|
|
// # Consensus
|
|
|
|
|
//
|
|
|
|
|
// > [Overwinter to Canopy inclusive, pre-NU5] nExpiryHeight MUST be less than
|
|
|
|
|
// > or equal to 499999999.
|
|
|
|
|
//
|
|
|
|
|
// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
|
|
|
|
|
validate_expiry_height_max(expiry_height, true, block_height, coinbase)
|
2021-11-25 16:37:24 -08:00
|
|
|
|
}
|
|
|
|
|
|
2022-02-10 16:32:57 -08:00
|
|
|
|
/// Returns `Ok(())` if the expiry height for a non coinbase transaction is
|
|
|
|
|
/// valid according to specifications [7.1] and [ZIP-203].
|
2021-11-25 16:37:24 -08:00
|
|
|
|
///
|
2022-05-30 13:12:11 -07:00
|
|
|
|
/// [7.1]: https://zips.z.cash/protocol/protocol.pdf#txnencodingandconsensus
|
|
|
|
|
/// [ZIP-203]: https://zips.z.cash/zip-0203
|
2021-11-25 16:37:24 -08:00
|
|
|
|
pub fn non_coinbase_expiry_height(
|
|
|
|
|
block_height: &Height,
|
|
|
|
|
transaction: &Transaction,
|
|
|
|
|
) -> Result<(), TransactionError> {
|
|
|
|
|
if transaction.is_overwintered() {
|
|
|
|
|
let expiry_height = transaction.expiry_height();
|
|
|
|
|
|
2022-02-04 10:07:20 -08:00
|
|
|
|
// # Consensus
|
|
|
|
|
//
|
|
|
|
|
// > [Overwinter to Canopy inclusive, pre-NU5] nExpiryHeight MUST be
|
|
|
|
|
// > less than or equal to 499999999.
|
|
|
|
|
//
|
|
|
|
|
// > [NU5 onward] nExpiryHeight MUST be less than or equal to 499999999
|
|
|
|
|
// > for non-coinbase transactions.
|
|
|
|
|
//
|
2022-02-10 16:32:57 -08:00
|
|
|
|
// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
|
|
|
|
|
validate_expiry_height_max(expiry_height, false, block_height, transaction)?;
|
|
|
|
|
|
|
|
|
|
// # Consensus
|
|
|
|
|
//
|
2022-02-04 10:07:20 -08:00
|
|
|
|
// > [Overwinter onward] If a transaction is not a coinbase transaction and its
|
2022-02-10 16:32:57 -08:00
|
|
|
|
// > nExpiryHeight field is nonzero, then it MUST NOT be mined at a block
|
|
|
|
|
// > height greater than its nExpiryHeight.
|
2022-02-04 10:07:20 -08:00
|
|
|
|
//
|
2022-02-10 16:32:57 -08:00
|
|
|
|
// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
|
2021-11-25 16:37:24 -08:00
|
|
|
|
validate_expiry_height_mined(expiry_height, block_height, transaction)?;
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-10 16:32:57 -08:00
|
|
|
|
/// Checks that the expiry height of a transaction does not exceed the maximal
|
|
|
|
|
/// value.
|
2021-11-25 16:37:24 -08:00
|
|
|
|
///
|
2022-02-10 16:32:57 -08:00
|
|
|
|
/// Only the `expiry_height` parameter is used for the check. The
|
|
|
|
|
/// remaining parameters are used to give details about the error when the check
|
|
|
|
|
/// fails.
|
2021-11-25 16:37:24 -08:00
|
|
|
|
fn validate_expiry_height_max(
|
|
|
|
|
expiry_height: Option<Height>,
|
|
|
|
|
is_coinbase: bool,
|
|
|
|
|
block_height: &Height,
|
|
|
|
|
transaction: &Transaction,
|
|
|
|
|
) -> Result<(), TransactionError> {
|
|
|
|
|
if let Some(expiry_height) = expiry_height {
|
|
|
|
|
if expiry_height > Height::MAX_EXPIRY_HEIGHT {
|
|
|
|
|
return Err(TransactionError::MaximumExpiryHeight {
|
|
|
|
|
expiry_height,
|
|
|
|
|
is_coinbase,
|
|
|
|
|
block_height: *block_height,
|
|
|
|
|
transaction_hash: transaction.hash(),
|
|
|
|
|
})?;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-10 16:32:57 -08:00
|
|
|
|
/// Checks that a transaction does not exceed its expiry height.
|
2021-11-25 16:37:24 -08:00
|
|
|
|
///
|
2022-02-10 16:32:57 -08:00
|
|
|
|
/// The `transaction` parameter is only used to give details about the error
|
|
|
|
|
/// when the check fails.
|
2021-11-25 16:37:24 -08:00
|
|
|
|
fn validate_expiry_height_mined(
|
|
|
|
|
expiry_height: Option<Height>,
|
|
|
|
|
block_height: &Height,
|
|
|
|
|
transaction: &Transaction,
|
|
|
|
|
) -> Result<(), TransactionError> {
|
|
|
|
|
if let Some(expiry_height) = expiry_height {
|
|
|
|
|
if *block_height > expiry_height {
|
|
|
|
|
return Err(TransactionError::ExpiredTransaction {
|
|
|
|
|
expiry_height,
|
|
|
|
|
block_height: *block_height,
|
|
|
|
|
transaction_hash: transaction.hash(),
|
|
|
|
|
})?;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|