docs: Transaction consensus rules: Size rules (#3461)

* refactor transaction size consensus rules

* quote mssing consensus rule

* nit

Co-authored-by: teor <teor@riseup.net>

* move consensus rule doc

Co-authored-by: teor <teor@riseup.net>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This commit is contained in:
Alfredo Garcia 2022-02-08 17:28:40 -03:00 committed by GitHub
parent fc3cba24f8
commit 29ad801a35
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 60 additions and 58 deletions

View File

@ -183,8 +183,12 @@ impl TrustedPreallocate for Action {
// and the signature is required,
// a valid max allocation can never exceed this size
const MAX: u64 = (MAX_BLOCK_BYTES - 1) / AUTHORIZED_ACTION_SIZE;
// # Consensus
//
// > [NU5 onward] nSpendsSapling, nOutputsSapling, and nActionsOrchard MUST all be less than 2^16.
// https://zips.z.cash/protocol/protocol.pdf#txnencodingandconsensus
//
// https://zips.z.cash/protocol/protocol.pdf#txnconsensus
//
// This acts as nActionsOrchard and is therefore subject to the rule.
// The maximum value is actually smaller due to the block size limit,
// but we ensure the 2^16 limit with a static assertion.
@ -206,13 +210,15 @@ bitflags! {
/// The spend and output flags are passed to the `Halo2Proof` verifier, which verifies
/// the relevant note spending and creation consensus rules.
///
/// Consensus rules:
/// # Consensus
///
/// > [NU5 onward] In a version 5 transaction, the reserved bits 2..7 of the flagsOrchard
/// > field MUST be zero.
///
/// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
///
/// - "In a version 5 transaction, the reserved bits 2..7 of the flagsOrchard field MUST be zero."
/// ([`bitflags`](https://docs.rs/bitflags/1.2.1/bitflags/index.html) restricts its values to the
/// set of valid flags)
/// - "In a version 5 coinbase transaction, the enableSpendsOrchard flag MUST be 0."
/// (Checked in zebra-consensus)
#[derive(Deserialize, Serialize)]
pub struct Flags: u8 {
/// Enable spending non-zero valued Orchard notes.

View File

@ -196,8 +196,12 @@ impl TrustedPreallocate for OutputInTransactionV4 {
// Since a serialized Vec<Output> uses at least one byte for its length,
// the max allocation can never exceed (MAX_BLOCK_BYTES - 1) / OUTPUT_SIZE
const MAX: u64 = (MAX_BLOCK_BYTES - 1) / OUTPUT_SIZE;
// # Consensus
//
// > [NU5 onward] nSpendsSapling, nOutputsSapling, and nActionsOrchard MUST all be less than 2^16.
// https://zips.z.cash/protocol/protocol.pdf#txnencodingandconsensus
//
// https://zips.z.cash/protocol/protocol.pdf#txnconsensus
//
// This acts as nOutputsSapling and is therefore subject to the rule.
// The maximum value is actually smaller due to the block size limit,
// but we ensure the 2^16 limit with a static assertion.

View File

@ -90,21 +90,6 @@ where
AnchorV: AnchorVariant + Clone,
{
/// The net value of Sapling spend transfers minus output transfers.
///
/// [`ShieldedData`] validates this [value balance consensus
/// rule](https://zips.z.cash/protocol/nu5.pdf#txnencodingandconsensus):
///
/// "If effectiveVersion = 4 and there are no Spend descriptions or Output
/// descriptions, then valueBalanceSapling MUST be 0."
///
/// During deserialization, this rule is checked when there are no spends and
/// no outputs.
///
/// During serialization, this rule is structurally validated by [`ShieldedData`].
/// `value_balance` is a field in [`ShieldedData`], which must have at least
/// one spend or output in its `transfers` field. If [`ShieldedData`] is `None`
/// then there can not possibly be any spends or outputs, and the
/// `value_balance` is always serialized as zero.
pub value_balance: Amount,
/// A bundle of spends and outputs, containing at least one spend or

View File

@ -323,8 +323,12 @@ impl TrustedPreallocate for SpendPrefixInTransactionV5 {
// and the associated fields are required,
// a valid max allocation can never exceed this size
const MAX: u64 = (MAX_BLOCK_BYTES - 1) / SHARED_ANCHOR_SPEND_SIZE;
// # Consensus
//
// > [NU5 onward] nSpendsSapling, nOutputsSapling, and nActionsOrchard MUST all be less than 2^16.
// https://zips.z.cash/protocol/protocol.pdf#txnencodingandconsensus
//
// https://zips.z.cash/protocol/protocol.pdf#txnconsensus
//
// This acts as nSpendsSapling and is therefore subject to the rule.
// The maximum value is actually smaller due to the block size limit,
// but we ensure the 2^16 limit with a static assertion.

View File

@ -223,14 +223,6 @@ impl Transaction {
// other properties
/// Does this transaction have transparent or shielded inputs?
///
/// "[Sapling onward] If effectiveVersion < 5, then at least one of tx_in_count,
/// nSpendsSapling, and nJoinSplit MUST be nonzero.
///
/// [NU5 onward] If effectiveVersion ≥ 5 then this condition MUST hold:
/// tx_in_count > 0 or nSpendsSapling > 0 or (nActionsOrchard > 0 and enableSpendsOrchard = 1)."
///
/// https://zips.z.cash/protocol/protocol.pdf#txnconsensus
pub fn has_transparent_or_shielded_inputs(&self) -> bool {
!self.inputs().is_empty() || self.has_shielded_inputs()
}
@ -249,14 +241,6 @@ impl Transaction {
}
/// Does this transaction have transparent or shielded outputs?
///
/// "[Sapling onward] If effectiveVersion < 5, then at least one of tx_out_count,
/// nOutputsSapling, and nJoinSplit MUST be nonzero.
///
/// [NU5 onward] If effectiveVersion ≥ 5 then this condition MUST hold:
/// tx_out_count > 0 or nOutputsSapling > 0 or (nActionsOrchard > 0 and enableOutputsOrchard = 1)."
///
/// https://zips.z.cash/protocol/protocol.pdf#txnconsensus
pub fn has_transparent_or_shielded_outputs(&self) -> bool {
!self.outputs().is_empty() || self.has_shielded_outputs()
}
@ -275,11 +259,6 @@ impl Transaction {
}
/// Does this transaction has at least one flag when we have at least one orchard action?
///
/// [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
pub fn has_enough_orchard_flags(&self) -> bool {
if self.version() < 5 || self.orchard_actions().count() == 0 {
return true;

View File

@ -567,6 +567,17 @@ impl ZcashSerialize for Transaction {
impl ZcashDeserialize for Transaction {
fn zcash_deserialize<R: io::Read>(reader: R) -> Result<Self, SerializationError> {
// # Consensus
//
// > [Pre-Sapling] The encoded size of the transaction MUST be less than or
// > equal to 100000 bytes.
//
// https://zips.z.cash/protocol/protocol.pdf#txnconsensus
//
// Zebra does not verify this rule because we checkpoint up to Canopy blocks, but:
// Since transactions must get mined into a block to be useful,
// we reject transactions that are larger than blocks.
//
// If the limit is reached, we'll get an UnexpectedEof error.
let mut limited_reader = reader.take(MAX_BLOCK_BYTES);
@ -687,9 +698,12 @@ impl ZcashDeserialize for Transaction {
outputs: shielded_outputs.try_into().expect("checked for outputs"),
})
} else {
// There are no shielded outputs and no shielded spends, so the value balance
// MUST be zero:
// https://zips.z.cash/protocol/protocol.pdf#txnencodingandconsensus
// # Consensus
//
// > [Sapling onward] If effectiveVersion = 4 and there are no Spend
// > descriptions or Output descriptions, then valueBalanceSapling MUST be 0.
//
// https://zips.z.cash/protocol/protocol.pdf#txnconsensus
if value_balance != 0 {
return Err(SerializationError::BadTransactionBalance);
}

View File

@ -60,19 +60,27 @@ pub fn lock_time_has_passed(
/// Checks that the transaction has inputs and outputs.
///
/// # Consensus
///
/// For `Transaction::V4`:
/// * At least one of `tx_in_count`, `nSpendsSapling`, and `nJoinSplit` MUST be non-zero.
/// * At least one of `tx_out_count`, `nOutputsSapling`, and `nJoinSplit` MUST be non-zero.
///
/// > [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.
///
/// For `Transaction::V5`:
/// * This condition must hold: `tx_in_count` > 0 or `nSpendsSapling` > 0 or
/// (`nActionsOrchard` > 0 and `enableSpendsOrchard` = 1)
/// * This condition must hold: `tx_out_count` > 0 or `nOutputsSapling` > 0 or
/// (`nActionsOrchard` > 0 and `enableOutputsOrchard` = 1)
///
/// > [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).
///
/// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
///
/// This check counts both `Coinbase` and `PrevOut` transparent inputs.
///
/// https://zips.z.cash/protocol/protocol.pdf#txnencodingandconsensus
pub fn has_inputs_and_outputs(tx: &Transaction) -> Result<(), TransactionError> {
if !tx.has_transparent_or_shielded_inputs() {
Err(TransactionError::NoInputs)
@ -85,11 +93,13 @@ pub fn has_inputs_and_outputs(tx: &Transaction) -> Result<(), TransactionError>
/// Checks that the transaction has enough orchard flags.
///
/// For `Transaction::V5` only:
/// * If `orchard_actions_count` > 0 then at least one of
/// `ENABLE_SPENDS|ENABLE_OUTPUTS` must be active.
/// # Consensus
///
/// https://zips.z.cash/protocol/protocol.pdf#txnencodingandconsensus
/// For `Transaction::V5` only:
///
/// > [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>
pub fn has_enough_orchard_flags(tx: &Transaction) -> Result<(), TransactionError> {
if !tx.has_enough_orchard_flags() {
return Err(TransactionError::NotEnoughFlags);