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, // and the signature is required,
// a valid max allocation can never exceed this size // a valid max allocation can never exceed this size
const MAX: u64 = (MAX_BLOCK_BYTES - 1) / AUTHORIZED_ACTION_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. // > [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. // This acts as nActionsOrchard and is therefore subject to the rule.
// The maximum value is actually smaller due to the block size limit, // The maximum value is actually smaller due to the block size limit,
// but we ensure the 2^16 limit with a static assertion. // 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 spend and output flags are passed to the `Halo2Proof` verifier, which verifies
/// the relevant note spending and creation consensus rules. /// 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 /// ([`bitflags`](https://docs.rs/bitflags/1.2.1/bitflags/index.html) restricts its values to the
/// set of valid flags) /// set of valid flags)
/// - "In a version 5 coinbase transaction, the enableSpendsOrchard flag MUST be 0."
/// (Checked in zebra-consensus)
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
pub struct Flags: u8 { pub struct Flags: u8 {
/// Enable spending non-zero valued Orchard notes. /// 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, // 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 // the max allocation can never exceed (MAX_BLOCK_BYTES - 1) / OUTPUT_SIZE
const MAX: u64 = (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. // > [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. // This acts as nOutputsSapling and is therefore subject to the rule.
// The maximum value is actually smaller due to the block size limit, // The maximum value is actually smaller due to the block size limit,
// but we ensure the 2^16 limit with a static assertion. // but we ensure the 2^16 limit with a static assertion.

View File

@ -90,21 +90,6 @@ where
AnchorV: AnchorVariant + Clone, AnchorV: AnchorVariant + Clone,
{ {
/// The net value of Sapling spend transfers minus output transfers. /// 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, pub value_balance: Amount,
/// A bundle of spends and outputs, containing at least one spend or /// 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, // and the associated fields are required,
// a valid max allocation can never exceed this size // a valid max allocation can never exceed this size
const MAX: u64 = (MAX_BLOCK_BYTES - 1) / SHARED_ANCHOR_SPEND_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. // > [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. // This acts as nSpendsSapling and is therefore subject to the rule.
// The maximum value is actually smaller due to the block size limit, // The maximum value is actually smaller due to the block size limit,
// but we ensure the 2^16 limit with a static assertion. // but we ensure the 2^16 limit with a static assertion.

View File

@ -223,14 +223,6 @@ impl Transaction {
// other properties // other properties
/// Does this transaction have transparent or shielded inputs? /// 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 { pub fn has_transparent_or_shielded_inputs(&self) -> bool {
!self.inputs().is_empty() || self.has_shielded_inputs() !self.inputs().is_empty() || self.has_shielded_inputs()
} }
@ -249,14 +241,6 @@ impl Transaction {
} }
/// Does this transaction have transparent or shielded outputs? /// 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 { pub fn has_transparent_or_shielded_outputs(&self) -> bool {
!self.outputs().is_empty() || self.has_shielded_outputs() !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? /// 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 { pub fn has_enough_orchard_flags(&self) -> bool {
if self.version() < 5 || self.orchard_actions().count() == 0 { if self.version() < 5 || self.orchard_actions().count() == 0 {
return true; return true;

View File

@ -567,6 +567,17 @@ impl ZcashSerialize for Transaction {
impl ZcashDeserialize for Transaction { impl ZcashDeserialize for Transaction {
fn zcash_deserialize<R: io::Read>(reader: R) -> Result<Self, SerializationError> { 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. // If the limit is reached, we'll get an UnexpectedEof error.
let mut limited_reader = reader.take(MAX_BLOCK_BYTES); 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"), outputs: shielded_outputs.try_into().expect("checked for outputs"),
}) })
} else { } else {
// There are no shielded outputs and no shielded spends, so the value balance // # Consensus
// MUST be zero: //
// https://zips.z.cash/protocol/protocol.pdf#txnencodingandconsensus // > [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 { if value_balance != 0 {
return Err(SerializationError::BadTransactionBalance); return Err(SerializationError::BadTransactionBalance);
} }

View File

@ -60,19 +60,27 @@ pub fn lock_time_has_passed(
/// Checks that the transaction has inputs and outputs. /// Checks that the transaction has inputs and outputs.
/// ///
/// # Consensus
///
/// For `Transaction::V4`: /// 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`: /// For `Transaction::V5`:
/// * 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:
/// * This condition must hold: `tx_out_count` > 0 or `nOutputsSapling` > 0 or /// > tx_in_count > 0 or nSpendsSapling > 0 or (nActionsOrchard > 0 and enableSpendsOrchard = 1).
/// (`nActionsOrchard` > 0 and `enableOutputsOrchard` = 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. /// 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> { pub fn has_inputs_and_outputs(tx: &Transaction) -> Result<(), TransactionError> {
if !tx.has_transparent_or_shielded_inputs() { if !tx.has_transparent_or_shielded_inputs() {
Err(TransactionError::NoInputs) 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. /// Checks that the transaction has enough orchard flags.
/// ///
/// For `Transaction::V5` only: /// # Consensus
/// * If `orchard_actions_count` > 0 then at least one of
/// `ENABLE_SPENDS|ENABLE_OUTPUTS` must be active.
/// ///
/// 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> { pub fn has_enough_orchard_flags(tx: &Transaction) -> Result<(), TransactionError> {
if !tx.has_enough_orchard_flags() { if !tx.has_enough_orchard_flags() {
return Err(TransactionError::NotEnoughFlags); return Err(TransactionError::NotEnoughFlags);